/*
@ITMillApache2LicenseForJavaFiles@
 */
/*
 * Copyright 2008 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.vaadin.terminal.gwt.client;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.impl.HistoryImpl;

/**
 * A slightly modified version of GWT's HistoryImplIE6 to bypass bug #2931. Also
 * combined with HistoryImplFrame.
 * 
 * This class should be removed if GWT issue 3890 gets resolved. (Also remember
 * to removed deferred binding rule from .gwt.xml file).
 */
public class HistoryImplIEVaadin extends HistoryImpl {

    private static native Element findHistoryFrame()
    /*-{
        return $doc.getElementById('__gwt_historyFrame');
    }-*/;

    private static native Element getTokenElement(Element historyFrame)
    /*-{
       // Initialize the history iframe.  If '__gwt_historyToken' already exists, then
       // we're probably backing into the app, so _don't_ set the iframe's location.
       if (historyFrame.contentWindow) {
           var doc = historyFrame.contentWindow.document;
           return doc.getElementById('__gwt_historyToken');
       }
    }-*/;

    protected Element historyFrame;

    @Override
    protected final void nativeUpdate(String historyToken) {
        /*
         * Must update the location hash since it isn't already correct.
         */
        updateHash(historyToken);
        navigateFrame(historyToken);
    }

    @Override
    protected final void nativeUpdateOnEvent(String historyToken) {
        updateHash(historyToken);
    }

    /**
     * Sanitizes an untrusted string to be used in an HTML context. NOTE: This
     * method of escaping strings should only be used on Internet Explorer.
     * 
     * @param maybeHtml
     *            untrusted string that may contain html
     * @return sanitized string
     */
    private static String escapeHtml(String maybeHtml) {
        final Element div = DOM.createDiv();
        DOM.setInnerText(div, maybeHtml);
        return DOM.getInnerHTML(div);
    }

    /**
     * For IE6, reading from $wnd.location.hash drops part of the fragment if
     * the fragment contains a '?'. To avoid this bug, we use location.href
     * instead.
     */
    private static native String getLocationHash()
    /*-{
       var href = $wnd.location.href;
       var hashLoc = href.lastIndexOf("#");
       return (hashLoc > 0) ? href.substring(hashLoc) : "";
    }-*/;

    @Override
    public boolean init() {
        historyFrame = findHistoryFrame();
        if (historyFrame == null) {
            return false;
        }

        initHistoryToken();

        // Initialize the history iframe. If a token element already exists,
        // then
        // we're probably backing into the app, so _don't_ create a new item.
        Element tokenElement = getTokenElement(historyFrame);
        if (tokenElement != null) {
            setToken(getTokenElementContent(tokenElement));
        } else {
            navigateFrame(getToken());
        }

        injectGlobalHandler();

        initUrlCheckTimer();
        return true;
    }

    protected native String getTokenElementContent(Element tokenElement)
    /*-{
        return tokenElement.innerText;
    }-*/;

    protected native void initHistoryToken()
    /*-{
       // Assume an empty token.
       var token = '';
       // Get the initial token from the url's hash component.
       var hash = @com.vaadin.terminal.gwt.client.HistoryImplIEVaadin::getLocationHash()();
       if (hash.length > 0) {
         try {
           token = this.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
         } catch (e) {
           // Clear the bad hash (this can't have been a valid token).
           $wnd.location.hash = '';
         }
       }
       @com.google.gwt.user.client.impl.HistoryImpl::setToken(Ljava/lang/String;)(token);
     }-*/;

    protected native void injectGlobalHandler()
    /*-{
       var historyImplRef = this;

       $wnd.__gwt_onHistoryLoad = function(token) {
         historyImplRef.@com.google.gwt.user.client.impl.HistoryImpl::newItemOnEvent(Ljava/lang/String;)(token);
       };
     }-*/;

    protected native void navigateFrame(String token)
    /*-{
       var escaped = @com.vaadin.terminal.gwt.client.HistoryImplIEVaadin::escapeHtml(Ljava/lang/String;)(token);
       var doc = this.@com.vaadin.terminal.gwt.client.HistoryImplIEVaadin::historyFrame.contentWindow.document;
       doc.open();
       doc.write('<html><body onload="if(parent.__gwt_onHistoryLoad)parent.__gwt_onHistoryLoad(__gwt_historyToken.innerText)"><div id="__gwt_historyToken">' + escaped + '</div></body></html>');
       doc.close();
     }-*/;

    protected native void updateHash(String token)
    /*-{
       $wnd.location.hash = this.@com.google.gwt.user.client.impl.HistoryImpl::encodeFragment(Ljava/lang/String;)(token);
     }-*/;

    private native void initUrlCheckTimer()
    /*-{
       // This is the URL check timer.  It detects when an unexpected change
       // occurs in the document's URL (e.g. when the user enters one manually
       // or selects a 'favorite', but only the #hash part changes).  When this
       // occurs, we _must_ reload the page.  This is because IE has a really
       // nasty bug that totally mangles its history stack and causes the location
       // bar in the UI to stop working under these circumstances.
       var historyImplRef = this;
       var urlChecker = function() {
         $wnd.setTimeout(urlChecker, 250);
         var hash = @com.vaadin.terminal.gwt.client.HistoryImplIEVaadin::getLocationHash()();
         if (hash.length > 0) {
           var token = '';
           try {
             token = historyImplRef.@com.google.gwt.user.client.impl.HistoryImpl::decodeFragment(Ljava/lang/String;)(hash.substring(1));
           } catch (e) {
             // If there's a bad hash, always reload. This could only happen if
             // if someone entered or linked to a bad url.
             $wnd.location.reload();
           }

           var historyToken = @com.google.gwt.user.client.impl.HistoryImpl::getToken()();
           if (token != historyToken) {
             $wnd.location.reload();
           }
         }
       };
       urlChecker();
     }-*/;

}
