// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
var Cu = Components.utils;
var Ci = Components.interfaces;
var Cc = Components.classes;
var Cr = Components.results;

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/ctypes.jsm");

Cu.import("resource://unity/unity-api.js");
Cu.import("resource://unity/unity-callback-manager.js");
Cu.import("resource://unity/unity-favicon-utils.js");
Cu.import("resource://unity/unity-misc-utils.js");
Cu.import("resource://unity/unity-popup-manager.js");
Cu.import("resource://unity/unity-preview-utils.js");
Cu.import("resource://unity/unity-webapps.js");
Cu.import("resource://unity/unity-window-helper.js");

var consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
var eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"].getService(Components.interfaces.nsIEffectiveTLDService);
var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);

prefs = prefs.getBranch("extensions.unity.");

function log(str) {
    /*extensions.unity.logging*/
    var enabled = false;
    try {
	enabled = prefs.getBoolPref("logging");
    } catch (x) {}
    if (enabled) {
        consoleService.logStringMessage(str);
    }
}

function setTimeout(callback, time) {
    var timer = Components.classes["@mozilla.org/timer;1"]
        .createInstance(Components.interfaces.nsITimer);
    timer.initWithCallback(callback, time, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
    return timer;
}

function toDataURL(uri, callback, sx, sy, sw, sh) {
    if (uri.match(/^\s{0,}file:/i))
	    return;
    if (uri.match(/^\s{0,}https:/i))
	    return;

    var mediator = Cc["@mozilla.org/appshell/window-mediator;1"]
	    .getService(Ci.nsIWindowMediator);
    var win = mediator.getMostRecentWindow("navigator:browser");
    var canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml",
                                              "canvas");
    var img = new win.Image();

    img.onload = function() {
        var width = img.width, height = img.height;

        var hasExtraArgs = sx || sx === 0;
        if (hasExtraArgs) {
            width = sw;
            height = sh;
        }

        var dx  = 0, dy = 0;

        if (width > height)
            dy = width - height;
        else
            dx = height - width;

        canvas.width = width + dx;
        canvas.height = height + dy;

        var ctx = canvas.getContext('2d');
        if (hasExtraArgs)
            ctx.drawImage(img, sx, sy, sw, sh, dx / 2, dy / 2, width, height);
        else
            ctx.drawImage(img, dx / 2, dy / 2);
        callback(true, canvas.toDataURL());
    };
    img.src = uri;
}

function requestIntegration(uwa, aWindow, domain, name, iconUrl, realInit) {
    function integrateActionCallback() {
	uwa.permissions_allow_domain(domain);

	realInit(name, iconUrl, true);
    }
    function dontAskActionCallback() {
	uwa.permissions_dontask_domain(domain);
    }

    if (uwa.permissions_get_domain_dontask(domain) || uwa.permissions_get_domain_allowed(domain)) {
	return;
    }

    var mainWindow = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
	.getInterface(Components.interfaces.nsIWebNavigation)
	.QueryInterface(Components.interfaces.nsIDocShellTreeItem)
	.rootTreeItem
	.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
        .getInterface(Components.interfaces.nsIDOMWindow);
    var PopupManager = new UnityPopupManager(mainWindow);

    PopupManager.requestIntegration(aWindow.document,
				    name, domain,
				    integrateActionCallback,
				    dontAskActionCallback);
}

function UnityObject (uwa, aWindow) {
    this._init(uwa, aWindow);
}

UnityObject.prototype = {
    _unity: null,
    uwa: null,

    _aWindow: null,
    _mainWindow: null,
    _windowHelper: null,
    _appRaiseCb: null,
    _appCloseCb: null,
    _active: false,

    _callbackManager: null,

    _init: function (uwa, aWindow) {
        this._events = [];
        this._unity = { context: null, onInitCallbacks: [], self: this };
        this.uwa = uwa;

	    this._callbackManager = new UnityCallbackManager();

        this._setWindow(aWindow);
    },

    _listenEvent: function(target, name, cb) {
        var descr = { target: target, name: name, cb: cb.bind(this) };

        target.addEventListener(name, descr.cb, false);

        this._events.push(descr);
    },

    _removeAllListeners: function() {
        for (var i = 0; i < this._events.length; i++) {
            let descr = this._events[i];
            descr.target.removeEventListener(descr.name, descr.cb, false);
        }
        this._events = [];
    },

    getAPI: function() {
        return makeAPI(this._initAPI.bind(this), toDataURL);
    },

    _initAPI: function(props) {
        var name = props.name, iconUrl = props.iconUrl, oninit = props.onInit, domain = props.domain, homepage = props.homepage, login = props.login, mimeTypes = props.mimeTypes;
        var toplevelWindow = this._windowHelper.FindToplevelContentWindow (this._aWindow);

        if (!mimeTypes) {
            mimeTypes = null;
        }
        if (!iconUrl) {
            iconUrl = null;
        }

        if (domain === undefined) {
            domain = this._aWindow.document.domain;
        } else {
            var currentBaseDomain = eTLDService.getBaseDomainFromHost(this._aWindow.document.domain);

            if ((domain.indexOf(currentBaseDomain,
                    domain.length - currentBaseDomain.length) == -1)) {
                throw Error("invalid supplied domain " + domain + " does not have base suffix (" + currentBaseDomain + ")");
            }
        }

        if (login) {
            Cc["@mozilla.org/observer-service;1"]
                .getService(Components.interfaces.nsIObserverService)
                .notifyObservers(null, "webapps-login", JSON.stringify({ login: login, domain: domain }));
        }

        this._props = {
            name: name,
            iconUrl: iconUrl,
            oninit: oninit,
            domain: domain,
            homepage: homepage,
            login: login,
            mimeTypes: mimeTypes
        };

        requestIntegration(this.uwa, this._aWindow, domain, name, iconUrl, this._realInit.bind(this));
    },

    _realInit: function(name, iconUrl, firstTime) {
	if (this._props.homepage == undefined) {
	    this._props.homepage = toplevelWindow.document.location.toString();
	}
	basename = this.uwa.unity_webapps_desktop_infos_build_desktop_basename(this._props.name, this._props.domain);
	if (!basename.isNull()) {
	    this.uwa.unity_webapps_application_repository_add_desktop_to_launcher("application://" + basename.readString() + ".desktop");
	}
    },

    _setWindow: function(aWindow) {
        this._aWindow = aWindow;
        this._windowHelper = new UnityWindowHelper(aWindow);
        this._mainWindow = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
	    .getInterface(Components.interfaces.nsIWebNavigation)
	    .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
	    .rootTreeItem
	    .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
            .getInterface(Components.interfaces.nsIDOMWindow);

        Services.obs.addObserver(this, "dom-window-destroyed", false);
        this._listenEvent(aWindow, "pagehide", function (event) {
            this._loseInterest(true);
        });
    },

    hasContext: function() {
        return ;
    },

    setActive: function(value) {
        this._active = false;
    },

    observe: function (aWindow, aTopic, aData) {
        // This can be dispatched from either the inner or outer window
        if (aWindow.window == this._aWindow && aTopic == "dom-window-destroyed") {
            Services.obs.removeObserver(this, "dom-window-destroyed");
            this._loseInterest(true);

            this._removeAllListeners();
            this._callbackManager.releaseAll();
        }
    },

    _loseInterest: function (abandoned) {
    }
}

var nope = Function();

function UnityObjectFactory() {
    this._init();
}

FindToplevelContentWindow = function (aWindow) {
    var toplevelContentWindow = aWindow;

    while (toplevelContentWindow.parent != toplevelContentWindow){
	toplevelContentWindow = toplevelContentWindow.parent;
    }

    return toplevelContentWindow;
}

UnityObjectFactory.prototype = {
    _init: function() {
        this.uwa = new UnityWebapps();
        this.service = this.uwa.service_new();
        this._windows = new WeakMap();
    },

    _get: function(aWindow) {
        aWindow = FindToplevelContentWindow(aWindow);

        if (!this._windows.has(aWindow.document)) {
            this._windows.set(aWindow.document, { unity: new UnityObject(this.uwa, aWindow) });
        }
        return this._windows.get(aWindow.document);
    },

    getForWindow: function(aWindow) {
        return this._get(aWindow).unity.getAPI();
    },

    getExistingForWindow: function(aWindow) {
        aWindow = FindToplevelContentWindow(aWindow);
        if (!this._windows.has(aWindow.document))
            return null;
        return this._get(aWindow).unity;
    }
};

var objectFactory = new UnityObjectFactory();

UnityGlobalPropertyInitializer = {
    _listeners: [],
    handle: function (aWindow) {
        if (!objectFactory.uwa.permissions_is_integration_allowed())
            return;

        var api = objectFactory.getForWindow(aWindow);
        function unity() {
        }
        unity.prototype = {
            __proto__: aWindow.wrappedJSObject.external,
            getUnityObject: function (version) {
                if (version === 1)
                    return api;
                throw new Error("incorrect version");
            },
            __exposedProps__: { 'getUnityObject': "r" }
        };
        aWindow.wrappedJSObject.external = new unity();

        for (i = 0; i < this._listeners.length; i++) {
            this._listeners[i](aWindow);
        }
    },

    connect: function(signalName, listener) {
        UnityGlobalPropertyInitializer._listeners.push(listener);
    },

    disconnectAll: function() {
        this._listeners = [];
    },

    getExistingForWindow: function(aWindow) {
        return objectFactory.getExistingForWindow(aWindow);
    }
};

var EXPORTED_SYMBOLS = [ "UnityGlobalPropertyInitializer" ];
