/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const EXPORTED_SYMBOLS = ["S4EStatusService"];

const CU = Components.utils;

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

function S4EStatusService(window, service, getters)
{
  this._window = window;
  this._service = service;
  this._getters = getters;

  this._overLinkService = new S4EOverlinkService(this._window, this._service, this);
}

S4EStatusService.prototype =
{
  _window:                 null,
  _service:                null,
  _getters:                null,
  _overLinkService:        null,

  _overLink:               { val: "", type: "" },
  _network:                { val: "", type: "" },
  _networkXHR:             { val: "", type: "" },
  _status:                 { val: "", type: "" },
  _jsStatus:               { val: "", type: "" },
  _defaultStatus:          { val: "", type: "" },

  _isFullScreen:           false,
  _isVideo:                false,

  _statusText:             { val: "", type: "" },
  _noUpdate:               false,
  _statusChromeTimeoutID:  0,
  _statusContentTimeoutID: 0,

  getCompositeStatusText: function()
  {
    return this._statusText.val;
  },

  getStatusText: function()
  {
    return this._status.val;
  },

  setNetworkStatus: function(status, busy)
  {
    if(busy)
    {
      this._network = { val: status, type: "network" };
      this._networkXHR = { val: "", type: "network xhr" };
    }
    else
    {
      this._networkXHR = { val: status, type: "network xhr" };
    }
    this.updateStatusField();
  },

  setStatusText: function(status)
  {
    this._status = { val: status, type: "status chrome" };
    this.updateStatusField();
  },

  setJSStatus: function(status)
  {
    this._jsStatus = { val: status, type: "status content" };
    this.updateStatusField();
  },

  setJSDefaultStatus: function(status)
  {
    // This was removed from Firefox in Bug 862917
  },

  setDefaultStatus: function(status)
  {
    this._defaultStatus = { val: status, type: "status chrome default" };
    this.updateStatusField();
  },

  setOverLink: function(link, aAnchor)
  {
    this._overLinkService.update(link, aAnchor);
  },

  setOverLinkInternal: function(link, aAnchor)
  {
    let status = this._service.status;
    let statusLinkOver = this._service.statusLinkOver;

    if(statusLinkOver)
    {
      link = link.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g, encodeURIComponent);
      if(status == statusLinkOver)
      {
        this._overLink = { val: link, type: "overLink", anchor: aAnchor };
        this.updateStatusField();
      }
      else
      {
        this.setStatusField(statusLinkOver, { val: link, type: "overLink", anchor: aAnchor }, true);
      }
    }
  },

  setNoUpdate: function(nu)
  {
    this._noUpdate = nu;
  },

  buildBinding: function() {
    let XULBWPropHandler = function(prop, oldval, newval) {
      CU.reportError("Attempt to modify XULBrowserWindow." + prop);
      return oldval;
    };

    ["updateStatusField", "onStatusChange"].forEach(function(prop)
    {
      this._window.XULBrowserWindow.unwatch(prop);
      this._window.XULBrowserWindow[prop] = function() {};
      this._window.XULBrowserWindow.watch(prop, XULBWPropHandler);
    }, this);

    ["getCompositeStatusText", "getStatusText", "setStatusText", "setJSStatus",
    "setJSDefaultStatus", "setDefaultStatus", "setOverLink"].forEach(function(prop)
    {
      this._window.XULBrowserWindow.unwatch(prop);
      this._window.XULBrowserWindow[prop] = this[prop].bind(this);
      this._window.XULBrowserWindow.watch(prop, XULBWPropHandler);
    }, this);

    let XULBWHandler = function(prop, oldval, newval) {
      if(!newval)
      {
        return newval;
      }
      CU.reportError("XULBrowserWindow changed. Updating S4E bindings.");
      this._window.setTimeout(function(self)
      {
        self.buildBinding();
      }, 0, this);
      return newval;
    };

    this._window.watch("XULBrowserWindow", XULBWHandler);
  },

  destroy: function()
  {
    // No need to unbind from the XULBrowserWindow, it's already null at this point

    this.clearTimer("_statusChromeTimeoutID");
    this.clearTimer("_statusContentTimeoutID");

    this._overLinkService.destroy();

    ["_overLink", "_network", "_networkXHR", "_status", "_jsStatus", "_defaultStatus",
    "_statusText", "_window", "_service", "_getters", "_overLinkService"].forEach(function(prop)
    {
      delete this[prop];
    }, this);
  },

  buildTextOrder: function()
  {
    this.__defineGetter__("_textOrder", function()
    {
      let textOrder = ["_overLink"];
      if(this._service.statusNetwork)
      {
        textOrder.push("_network");
        if(this._service.statusNetworkXHR)
        {
          textOrder.push("_networkXHR");
        }
      }
      textOrder.push("_status", "_jsStatus");
      if(this._service.statusDefault)
      {
        textOrder.push("_defaultStatus");
      }

      delete this._textOrder;
      return this._textOrder = textOrder;
    });
  },

  updateStatusField: function(force)
  {
    let text = { val: "", type: "" };
    for(let i = 0; !text.val && i < this._textOrder.length; i++)
    {
      text = this[this._textOrder[i]];
    }

    if(this._statusText.val != text.val || force)
    {
      if(this._noUpdate)
      {
        return;
      }

      this._statusText = text;

      this.setStatusField(this._service.status, text, false);

      if(text.val && this._service.statusTimeout)
      {
        this.setTimer(text.type);
      }
    }
  },

  setFullScreenState: function(isFullScreen, isVideo)
  {
    this._isFullScreen = isFullScreen;
    this._isVideo = isFullScreen && isVideo;

    this.clearStatusField();
    this.updateStatusField(true);
  },

  setTimer: function(type)
  {
    let typeArgs = type.split(" ", 3);

    if(typeArgs.length < 2 || typeArgs[0] != "status")
    {
      return;
    }

    if(typeArgs[1] == "chrome")
    {
      this.clearTimer("_statusChromeTimeoutID");
      this._statusChromeTimeoutID = this._window.setTimeout(function(self, isDefault)
      {
        self._statusChromeTimeoutID = 0;
        if(isDefault)
        {
          self.setDefaultStatus("");
        }
        else
        {
          self.setStatusText("");
        }
      }, this._service.statusTimeout, this, (typeArgs.length == 3 && typeArgs[2] == "default"));
    }
    else
    {
      this.clearTimer("_statusContentTimeoutID");
      this._statusContentTimeoutID = this._window.setTimeout(function(self)
      {
        self._statusContentTimeoutID = 0;
        self.setJSStatus("");
      }, this._service.statusTimeout, this);
    }
  },

  clearTimer: function(timerName)
  {
    if(this[timerName] != 0)
    {
      this._window.clearTimeout(this[timerName]);
      this[timerName] = 0;
    }
  },

  clearStatusField: function()
  {
    this._getters.statusOverlay.value = "";

    let status_label = this._getters.statusWidgetLabel;
    if(status_label)
    {
      status_label.value = "";
    }

  },

  setStatusField: function(location, text, allowTooltip)
  {
    if(!location)
    {
      return;
    }

    let label = null;

    if(this._isFullScreen)
    {
      switch(location)
      {
        case 1: // Toolbar
          location = 3
          break;
        case 2: // URL bar
          if(Services.prefs.getBoolPref("browser.fullscreen.autohide"))
          {
            location = 3
          }
          break;
      }
    }

    switch(location)
    {
      case 1: // Toolbar
        label = this._getters.statusWidgetLabel;
        break;
      case 2: // URL Bar
        break;
      case 3: // Popup
      default:
        if(this._isVideo)
        {
          return;
        }
        label = this._getters.statusOverlay;
        break;
    }

    if(label)
    {
      label.setAttribute("previoustype", label.getAttribute("type"));
      label.setAttribute("type", text.type);
      label.value = text.val;
      label.setAttribute("crop", text.type == "overLink" ? "center" : "end");
    }
  }
};

function S4EOverlinkService(window, service, statusService) {
  this._window = window;
  this._service = service;
  this._statusService = statusService;
}

S4EOverlinkService.prototype =
{
  _window:        null,
  _service:       null,
  _statusService: null,

  _timer:         0,
  _currentLink:   { link: "", anchor: null },
  _pendingLink:   { link: "", anchor: null },
  _listening:     false,

  update: function(aLink, aAnchor)
  {
    this.clearTimer();
    this.stopListen();
    this._pendingLink = { link: aLink, anchor: aAnchor };

    if(!aLink)
    {
      if(this._window.XULBrowserWindow.hideOverLinkImmediately || !this._service.statusLinkOverDelayHide)
      {
        this._show();
      }
      else
      {
        this._showDelayed();
      }
    }
    else if(this._currentLink.link || !this._service.statusLinkOverDelayShow)
    {
      this._show();
    }
    else
    {
      this._showDelayed();
      this.startListen();
    }
  },

  destroy: function()
  {
    this.clearTimer();
    this.stopListen();

    ["_currentLink", "_pendingLink",  "_statusService", "_window"].forEach(function(prop)
    {
      delete this[prop];
    }, this);
  },

  startListen: function()
  {
    if(!this._listening)
    {
      this._window.addEventListener("mousemove", this, true);
      this._listening = true;
    }
  },

  stopListen: function()
  {
    if(this._listening)
    {
      this._window.removeEventListener("mousemove", this, true);
      this._listening = false;
    }
  },

  clearTimer: function()
  {
    if(this._timer != 0)
    {
      this._window.clearTimeout(this._timer);
      this._timer = 0;
    }
  },

  handleEvent: function(event)
  {
    switch(event.type)
    {
      case "mousemove":
        this.clearTimer();
        this._showDelayed();
    }
  },

  _showDelayed: function()
  {
    let delay = ((this._pendingLink.link)
      ? this._service.statusLinkOverDelayShow
      : this._service.statusLinkOverDelayHide);

    this._timer = this._window.setTimeout(function(self)
    {
      self._timer = 0;
      self._show();
      self.stopListen();
    }, delay, this);
  },

  _show: function()
  {
    this._currentLink = this._pendingLink;
    this._statusService.setOverLinkInternal(this._currentLink.link, this._currentLink.anchor);
  }
};