package com.ited.gwt.history.client;

import static com.ited.lang.misc.UtilsMisc.isNotNull;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.ited.lang.misc.UtilsString;

/**
 * @author Ed Bras
 */
public class SimpleBrowserHistory {

	private String token;

	private String urlHistoryTokenPrefix;

	private boolean isEventFireAllowedDuringCreation;

	SimpleBrowserHistory() {
	}

	protected final SimpleBrowserHistory setEventFireAllowedDuringCreation(final boolean yes) {
		this.isEventFireAllowedDuringCreation = yes;
		return this;
	}

	protected final boolean isEventFireAllowedDuringCreation() {
		return this.isEventFireAllowedDuringCreation;
	}

	protected void init() {
		attachListener();
		final String historyToken = createStartTokenValue();
		if (UtilsString.hasContentTrim(historyToken)) {
			getBrowserHistory().newItem(historyToken, isEventFireAllowedDuringCreation());
		}
	}

	protected String getDecodedHash() {
		final String hashToken = Window.Location.getHash();
		return UtilsString.isEmptyTrim(hashToken) ? null : decodeHistoryToken(hashToken.substring(1));
	}

	protected String createStartTokenValue() {
		return getDecodedHash();
	}

	protected final void setToken(final String token) {
		this.token = token;
	}

	protected final String getToken() {
		return this.token;
	}

	protected final boolean hasToken() {
		return isNotNull(getToken());
	}

	protected final void setUrlHistoryTokenPrefix(final String urlHistoryTokenPrefix) {
		this.urlHistoryTokenPrefix = urlHistoryTokenPrefix;
	}

	protected final String getUrlHistoryTokenPrefix() {
		return this.urlHistoryTokenPrefix;
	}

	protected final boolean hasUrlHistoryTokenPrefix() {
		return isNotNull(getUrlHistoryTokenPrefix());
	}

	// @formatter:off

    protected void newToken(final String historyToken) {
   		newTokenIntern(hasUrlHistoryTokenPrefix() ?  getUrlHistoryTokenPrefix() +  historyToken : historyToken);
     }


    protected native void newTokenIntern(String historyToken) /*-{
        $wnd.location.hash = historyToken;
    }-*/;

    protected void replaceToken(final String historyToken) {
        Window.Location.replace("#" + historyToken);
    }

    // Only kept in deferred binding to allow mocking frameworks to intercept calls
    protected native String decodeHistoryToken(String historyToken) /*-{
        return $wnd.decodeURI(historyToken.replace("%23", "#"));
    }-*/;

    // Only kept in deferred binding to allow mocking frameworks to intercept calls
    protected native String encodeHistoryToken(String historyToken) /*-{
        // encodeURI() does *not* encode the '#' character.
        return $wnd.encodeURI(historyToken).replace("#", "%23");
    }-*/;

    protected native void attachListener() /*-{
	    // We explicitly use the third parameter for capture, since Firefox before version 6 throws an exception if the parameter is missing.
	    // See: https://developer.mozilla.org/es/docs/DOM/elemento.addEventListener#Gecko_notes
	    var handler = $entry(this.@com.ited.gwt.history.client.SimpleBrowserHistory::onHashChanged());
	    $wnd.addEventListener('hashchange', handler, false);
	}-*/;


    /**
	 * History implementation for IE8.
	 */
	static class HistoryImplIE8 extends SimpleBrowserHistory {
        @Override
        protected native void attachListener() /*-{
          var handler = $entry(this.@com.ited.gwt.history.client.SimpleBrowserHistory::onHashChanged());
          var oldHandler = $wnd.onhashchange;
          $wnd.onhashchange = function() {
            var ex;

            try {
              handler();
            } catch(e) {
              ex = e;
            }

            if (oldHandler != null) {
              try {
                oldHandler();
              } catch(e) {
                ex = ex || e;
              }
            }

            if (ex != null) {
              throw ex;
            }
          };
        }-*/;
      }
	// @formatter:on

	private static SimpleBrowserHistory createInstance() {
		final SimpleBrowserHistory instance = GWT.create(SimpleBrowserHistory.class);
		instance.init();
		return instance;
	}

	// this is called from JS when the native onhashchange occurs
	private void onHashChanged() {
		// We guard against firing events twice, some browser (e.g. safari) tend to
		// fire events on startup if HTML5 pushstate is used.
		final String hashToken = getDecodedHash();
		if (!hashToken.equals(getToken())) {
			setToken(hashToken);
			getBrowserHistory().fireCurrentHistoryState();
		}
	}

	private BrowserHistory getBrowserHistory() {
		return BrowserHistory.getInstance();
	}

}
