package ${package};

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;

import org.apache.wicket.Application;
import org.apache.wicket.protocol.http.HttpSessionStore;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.protocol.http.WebRequest;
import org.apache.wicket.session.ISessionStore;
import org.apache.wicket.util.listener.ChangeListenerSet;
import org.apache.wicket.util.listener.IChangeListener;
import org.apache.wicket.util.time.Duration;
import org.apache.wicket.util.time.Time;
import org.apache.wicket.util.watch.IModifiable;
import org.apache.wicket.util.watch.IModificationWatcher;

import com.google.apphosting.api.ApiProxy;

public class WicketApplication extends WebApplication {
	private boolean isLocalMode = true;

	private static final Logger LOGGER = Logger
			.getLogger(WicketApplication.class.getName());

	public WicketApplication() {
	}

	public Class<IndexPage> getHomePage() {
		return IndexPage.class;
	}


	@Override
	public String getConfigurationType() {
		isLocalMode =
			super.getServletContext().getServerInfo().startsWith(
					"Google App Engine Development")
					|| super.getServletContext().getServerInfo().startsWith(
							"Wicket Mock Test Environment");
		return isLocalMode ? Application.DEVELOPMENT : Application.DEPLOYMENT;
	}

	@Override
	protected void init() {
		super.init();
		LOGGER.info("getServletContext().getServerInfo()="
				+ getServletContext().getServerInfo());
		LOGGER.info("configurationType=\"" + getConfigurationType()
				+ "\"で動作します。");
		LOGGER.info("[ENV]Application Id="
				+ ApiProxy.getCurrentEnvironment().getAppId());
		LOGGER.info("[ENV]Application Version="
				+ ApiProxy.getCurrentEnvironment().getVersionId());
		if (isLocalMode) {
			getResourceSettings().setResourceWatcher(
					new AppEngineModificationWatcher());
		}
	}

	@Override
	protected ISessionStore newSessionStore() {
		return new HttpSessionStore(this);
	}
	
	@Override
	protected WebRequest newWebRequest(HttpServletRequest servletRequest) {
		if (isLocalMode) {
			getResourceSettings().getResourceWatcher(true).start(
					getResourceSettings().getResourcePollFrequency());
		}
		return super.newWebRequest(servletRequest);
	}

	/**
	 * AppEngineSDKの制限にひっかからない{@link IModificationWatcher}の実装。
	 * <p>
	 * DEVELOPMENTモードで使用する。
	 * </p>
	 * <ul>
	 * <li>http://blog.manufacturers-hub.com/article/123006024.html</li>
	 * </ul>
	 * <p>
	 * 上記URLのサイトで紹介されていていた、wicket-1.4-RC6から使用できる機能を活用したモジュール。
	 * </p>
	 * 
	 * @author Shinichi Ogawa
	 */
	private static class AppEngineModificationWatcher implements
			IModificationWatcher {

		private static class Entry {

			Time lastModifiedTime;

			ChangeListenerSet listeners = new ChangeListenerSet();

			IModifiable modifiable;
		}

		private final Map<IModifiable, Entry> modifiableToEntry = new ConcurrentHashMap<IModifiable, Entry>();

		public boolean add(IModifiable modifiable, IChangeListener listener) {

			Entry entry = modifiableToEntry.get(modifiable);
			if (entry == null) {
				Time lastModifiedTime = modifiable.lastModifiedTime();
				if (lastModifiedTime != null) {
					Entry newEntry = new Entry();

					newEntry.modifiable = modifiable;
					newEntry.lastModifiedTime = lastModifiedTime;
					newEntry.listeners.add(listener);

					modifiableToEntry.put(modifiable, newEntry);
				}

				return true;
			} else {
				return entry.listeners.add(listener);
			}
		}

		public void destroy() {
		}

		public Set<IModifiable> getEntries() {
			return modifiableToEntry.keySet();
		}

		public IModifiable remove(IModifiable modifiable) {
			Entry entry = modifiableToEntry.remove(modifiable);
			if (entry != null) {
				return entry.modifiable;
			}

			return null;
		}

		public void start(Duration pollFrequency) {
			Iterator<Entry> iterator = new ArrayList<Entry>(modifiableToEntry
					.values()).iterator();
			while (iterator.hasNext()) {
				Entry entry = iterator.next();
				Time modifiableLastModified = entry.modifiable
						.lastModifiedTime();
				if ((modifiableLastModified != null)
						&& modifiableLastModified.after(entry.lastModifiedTime)) {
					entry.listeners.notifyListeners();
					entry.lastModifiedTime = modifiableLastModified;
				}
			}
		}
	}
}
