/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;

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

const DEBUG = false; // set to false to suppress debug messages

const DOMWIFIMANAGER_CONTRACTID = "@mozilla.org/wifimanager;1";
const DOMWIFIMANAGER_CID        = Components.ID("{c9b5f09e-25d2-40ca-aef4-c4d13d93c706}");

function MozWifiNetwork() {
}

MozWifiNetwork.prototype = {

  init: function(aWindow) {
    this._window = aWindow;
  },

  __init: function(obj) {
    for (let key in obj) {
      this[key] = obj[key];
    }
  },

  classID: Components.ID("{c01fd751-43c0-460a-8b64-abf652ec7220}"),
  contractID: "@mozilla.org/mozwifinetwork;1",
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                         Ci.nsIDOMGlobalPropertyInitializer])
};

function MozWifiConnection(obj) {
  this.status = obj.status;
  this.network = obj.network;
}

MozWifiConnection.prototype = {
  classID: Components.ID("{23579da4-201b-4319-bd42-9b7f337343ac}"),
  contractID: "@mozilla.org/mozwificonnection;1",
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
};

function MozWifiConnectionInfo(obj) {
  this.signalStrength = obj.signalStrength;
  this.relSignalStrength = obj.relSignalStrength;
  this.linkSpeed = obj.linkSpeed;
  this.ipAddress = obj.ipAddress;
}

MozWifiConnectionInfo.prototype = {
  classID: Components.ID("{83670352-6ed4-4c35-8de9-402296a1959c}"),
  contractID: "@mozilla.org/mozwificonnectioninfo;1",
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
}

function MozWifiCapabilities(obj) {
  this.security = obj.security;
  this.eapMethod = obj.eapMethod;
  this.eapPhase2 = obj.eapPhase2;
  this.certificate = obj.certificate;
}

MozWifiCapabilities.prototype = {
  classID: Components.ID("08c88ece-8092-481b-863b-5515a52e411a"),
  contractID: "@mozilla.org/mozwificapabilities;1",
  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
}

function DOMWifiManager() {
  this.defineEventHandlerGetterSetter("onstatuschange");
  this.defineEventHandlerGetterSetter("onconnectioninfoupdate");
  this.defineEventHandlerGetterSetter("onenabled");
  this.defineEventHandlerGetterSetter("ondisabled");
  this.defineEventHandlerGetterSetter("onstationinfoupdate");
}

DOMWifiManager.prototype = {
  __proto__: DOMRequestIpcHelper.prototype,
  classDescription: "DOMWifiManager",
  classID: DOMWIFIMANAGER_CID,
  contractID: DOMWIFIMANAGER_CONTRACTID,
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
                                         Ci.nsISupportsWeakReference,
                                         Ci.nsIObserver]),

  // nsIDOMGlobalPropertyInitializer implementation
  init: function(aWindow) {
    // Maintain this state for synchronous APIs.
    this._currentNetwork = null;
    this._connectionStatus = "disconnected";
    this._enabled = false;
    this._lastConnectionInfo = null;
    this._capabilities = null;
    this._stationNumber = 0;

    const messages = ["WifiManager:getNetworks:Return:OK", "WifiManager:getNetworks:Return:NO",
                      "WifiManager:getKnownNetworks:Return:OK", "WifiManager:getKnownNetworks:Return:NO",
                      "WifiManager:associate:Return:OK", "WifiManager:associate:Return:NO",
                      "WifiManager:forget:Return:OK", "WifiManager:forget:Return:NO",
                      "WifiManager:wps:Return:OK", "WifiManager:wps:Return:NO",
                      "WifiManager:setPowerSavingMode:Return:OK", "WifiManager:setPowerSavingMode:Return:NO",
                      "WifiManager:setHttpProxy:Return:OK", "WifiManager:setHttpProxy:Return:NO",
                      "WifiManager:setStaticIpMode:Return:OK", "WifiManager:setStaticIpMode:Return:NO",
                      "WifiManager:importCert:Return:OK", "WifiManager:importCert:Return:NO",
                      "WifiManager:getImportedCerts:Return:OK", "WifiManager:getImportedCerts:Return:NO",
                      "WifiManager:deleteCert:Return:OK", "WifiManager:deleteCert:Return:NO",
                      "WifiManager:setWifiEnabled:Return:OK", "WifiManager:setWifiEnabled:Return:NO",
                      "WifiManager:wifiDown", "WifiManager:wifiUp",
                      "WifiManager:onconnecting", "WifiManager:onassociate",
                      "WifiManager:onconnect", "WifiManager:ondisconnect",
                      "WifiManager:onwpstimeout", "WifiManager:onwpsfail",
                      "WifiManager:onwpsoverlap", "WifiManager:connectioninfoupdate",
                      "WifiManager:onauthenticating", "WifiManager:onconnectingfailed",
                      "WifiManager:stationinfoupdate"];
    this.initDOMRequestHelper(aWindow, messages);
    this._mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender);

    var state = this._mm.sendSyncMessage("WifiManager:getState")[0];
    if (state) {
      this._currentNetwork = this._convertWifiNetwork(state.network);
      this._lastConnectionInfo = this._convertConnectionInfo(state.connectionInfo);
      this._enabled = state.enabled;
      this._connectionStatus = state.status;
      this._macAddress = state.macAddress;
      this._capabilities = this._convertWifiCapabilities(state.capabilities);
    } else {
      this._currentNetwork = null;
      this._lastConnectionInfo = null;
      this._enabled = false;
      this._connectionStatus = "disconnected";
      this._macAddress = "";
    }
  },

  _convertWifiNetworkToJSON: function(aNetwork) {
    let json = {};

    for (let key in aNetwork) {
      // In WifiWorker.js there are lots of check using "key in network".
      // So if the value of any property of WifiNetwork is undefined, do not clone it.
      if (aNetwork[key] != undefined) {
        json[key] = aNetwork[key];
      }
    }
    return json;
  },

  _convertWifiNetwork: function(aNetwork) {
    let network = aNetwork ? new this._window.MozWifiNetwork(aNetwork) : null;
    return network;
  },

  _convertWifiNetworks: function(aNetworks) {
    let networks = new this._window.Array();
    for (let i in aNetworks) {
      networks.push(this._convertWifiNetwork(aNetworks[i]));
    }
    return networks;
  },

  _convertConnection: function(aConn) {
    let conn = aConn ? new MozWifiConnection(aConn) : null;
    return conn;
  },

  _convertConnectionInfo: function(aInfo) {
    let info = aInfo ? new MozWifiConnectionInfo(aInfo) : null;
    return info;
  },

  _convertWifiCapabilities: function(aCapabilities) {
    let capabilities = aCapabilities ?
                         new MozWifiCapabilities(aCapabilities) : null;
    return capabilities;
  },

  _sendMessageForRequest: function(name, data, request) {
    let id = this.getRequestId(request);
    this._mm.sendAsyncMessage(name, { data: data, rid: id, mid: this._id });
  },

  receiveMessage: function(aMessage) {
    let msg = aMessage.json;
    if (msg.mid && msg.mid != this._id)
      return;

    let request;
    if (msg.rid) {
      request = this.takeRequest(msg.rid);
      if (!request) {
        return;
      }
    }

    switch (aMessage.name) {
      case "WifiManager:setWifiEnabled:Return:OK":
        Services.DOMRequest.fireSuccess(request, msg.data);
        break;

      case "WifiManager:setWifiEnabled:Return:NO":
        Services.DOMRequest.fireError(request, "Unable to enable/disable Wifi");
        break;

      case "WifiManager:getNetworks:Return:OK":
        Services.DOMRequest.fireSuccess(request, this._convertWifiNetworks(msg.data));
        break;

      case "WifiManager:getNetworks:Return:NO":
        Services.DOMRequest.fireError(request, "Unable to scan for networks");
        break;

      case "WifiManager:getKnownNetworks:Return:OK":
        Services.DOMRequest.fireSuccess(request, this._convertWifiNetworks(msg.data));
        break;

      case "WifiManager:getKnownNetworks:Return:NO":
        Services.DOMRequest.fireError(request, "Unable to get known networks");
        break;

      case "WifiManager:associate:Return:OK":
        Services.DOMRequest.fireSuccess(request, true);
        break;

      case "WifiManager:associate:Return:NO":
        Services.DOMRequest.fireError(request, "Unable to add the network");
        break;

      case "WifiManager:forget:Return:OK":
        Services.DOMRequest.fireSuccess(request, true);
        break;

      case "WifiManager:forget:Return:NO":
        Services.DOMRequest.fireError(request, msg.data);
        break;

      case "WifiManager:wps:Return:OK":
        Services.DOMRequest.fireSuccess(request, msg.data);
        break;

      case "WifiManager:wps:Return:NO":
        Services.DOMRequest.fireError(request, msg.data);
        break;

      case "WifiManager:setPowerSavingMode:Return:OK":
        Services.DOMRequest.fireSuccess(request, msg.data);
        break;

      case "WifiManager:setPowerSavingMode:Return:NO":
        Services.DOMRequest.fireError(request, msg.data);
        break;

      case "WifiManager:setHttpProxy:Return:OK":
        Services.DOMRequest.fireSuccess(request, msg.data);
        break;

      case "WifiManager:setHttpProxy:Return:NO":
        Services.DOMRequest.fireError(request, msg.data);
        break;

      case "WifiManager:setStaticIpMode:Return:OK":
        Services.DOMRequest.fireSuccess(request, msg.data);
        break;

      case "WifiManager:setStaticIpMode:Return:NO":
        Services.DOMRequest.fireError(request, msg.data);
        break;

      case "WifiManager:importCert:Return:OK":
        Services.DOMRequest.fireSuccess(request, Cu.cloneInto(msg.data, this._window));
        break;

      case "WifiManager:importCert:Return:NO":
        Services.DOMRequest.fireError(request, msg.data);
        break;

      case "WifiManager:getImportedCerts:Return:OK":
        Services.DOMRequest.fireSuccess(request, Cu.cloneInto(msg.data, this._window));
        break;

      case "WifiManager:getImportedCerts:Return:NO":
        Services.DOMRequest.fireError(request, msg.data);
        break;

      case "WifiManager:deleteCert:Return:OK":
        Services.DOMRequest.fireSuccess(request, msg.data);
        break;

      case "WifiManager:deleteCert:Return:NO":
        Services.DOMRequest.fireError(request, msg.data);
        break;

      case "WifiManager:wifiDown":
        this._enabled = false;
        this._currentNetwork = null;
        this._fireEnabledOrDisabled(false);
        break;

      case "WifiManager:wifiUp":
        this._enabled = true;
        this._macAddress = msg.macAddress;
        this._fireEnabledOrDisabled(true);
        break;

      case "WifiManager:onconnecting":
        this._currentNetwork = this._convertWifiNetwork(msg.network);
        this._connectionStatus = "connecting";
        this._fireStatusChangeEvent(msg.network);
        break;

      case "WifiManager:onassociate":
        this._currentNetwork = this._convertWifiNetwork(msg.network);
        this._connectionStatus = "associated";
        this._fireStatusChangeEvent(msg.network);
        break;

      case "WifiManager:onconnect":
        this._currentNetwork = this._convertWifiNetwork(msg.network);
        this._connectionStatus = "connected";
        this._fireStatusChangeEvent(msg.network);
        break;

      case "WifiManager:ondisconnect":
        this._currentNetwork = null;
        this._connectionStatus = "disconnected";
        this._lastConnectionInfo = null;
        this._fireStatusChangeEvent(msg.network);
        break;

      case "WifiManager:onwpstimeout":
        this._currentNetwork = null;
        this._connectionStatus = "wps-timedout";
        this._lastConnectionInfo = null;
        this._fireStatusChangeEvent(msg.network);
        break;

      case "WifiManager:onwpsfail":
        this._currentNetwork = null;
        this._connectionStatus = "wps-failed";
        this._lastConnectionInfo = null;
        this._fireStatusChangeEvent(msg.network);
        break;

      case "WifiManager:onwpsoverlap":
        this._currentNetwork = null;
        this._connectionStatus = "wps-overlapped";
        this._lastConnectionInfo = null;
        this._fireStatusChangeEvent(msg.network);
        break;

      case "WifiManager:connectioninfoupdate":
        this._lastConnectionInfo = this._convertConnectionInfo(msg);
        this._fireConnectionInfoUpdate(msg);
        break;
      case "WifiManager:onconnectingfailed":
        this._currentNetwork = null;
        this._connectionStatus = "connectingfailed";
        this._lastConnectionInfo = null;
        this._fireStatusChangeEvent(msg.network);
        break;
      case "WifiManager:onauthenticating":
        this._currentNetwork = this._convertWifiNetwork(msg.network);
        this._connectionStatus = "authenticating";
        this._fireStatusChangeEvent(msg.network);
        break;
      case "WifiManager:stationinfoupdate":
        this._stationNumber = msg.station;
        this._fireStationInfoUpdate(msg);
        break;
    }
  },

  _fireStatusChangeEvent: function StatusChangeEvent(aNetwork) {
    var event = new this._window.MozWifiStatusChangeEvent("statuschange",
                                                          { network: this._convertWifiNetwork(aNetwork),
                                                            status: this._connectionStatus
                                                          });
    this.__DOM_IMPL__.dispatchEvent(event);
  },

  _fireConnectionInfoUpdate: function onConnectionInfoUpdate(info) {
    var evt = new this._window.MozWifiConnectionInfoEvent("connectioninfoupdate",
                                                          { network: this._currentNetwork,
                                                            signalStrength: info.signalStrength,
                                                            relSignalStrength: info.relSignalStrength,
                                                            linkSpeed: info.linkSpeed,
                                                            ipAddress: info.ipAddress,
                                                          });
    this.__DOM_IMPL__.dispatchEvent(evt);
  },

  _fireEnabledOrDisabled: function enabledDisabled(enabled) {
    var evt = new this._window.Event(enabled ? "enabled" : "disabled");
    this.__DOM_IMPL__.dispatchEvent(evt);
  },

  _fireStationInfoUpdate: function onStationInfoUpdate(info) {
    var evt = new this._window.MozWifiStationInfoEvent("stationinfoupdate",
                                                       { station: this._stationNumber}
                                                      );
    this.__DOM_IMPL__.dispatchEvent(evt);
  },

  setWifiEnabled: function setWifiEnabled(enabled) {
    var request = this.createRequest();
    this._sendMessageForRequest("WifiManager:setWifiEnabled", enabled, request);
    return request;
  },

  getNetworks: function getNetworks() {
    var request = this.createRequest();
    this._sendMessageForRequest("WifiManager:getNetworks", null, request);
    return request;
  },

  getKnownNetworks: function getKnownNetworks() {
    var request = this.createRequest();
    this._sendMessageForRequest("WifiManager:getKnownNetworks", null, request);
    return request;
  },

  associate: function associate(network) {
    var request = this.createRequest();
    this._sendMessageForRequest("WifiManager:associate",
                                this._convertWifiNetworkToJSON(network), request);
    return request;
  },

  forget: function forget(network) {
    var request = this.createRequest();
    this._sendMessageForRequest("WifiManager:forget",
                                this._convertWifiNetworkToJSON(network), request);
    return request;
  },

  wps: function wps(detail) {
    var request = this.createRequest();
    this._sendMessageForRequest("WifiManager:wps", detail, request);
    return request;
  },

  setPowerSavingMode: function setPowerSavingMode(enabled) {
    var request = this.createRequest();
    this._sendMessageForRequest("WifiManager:setPowerSavingMode", enabled, request);
    return request;
  },

  setHttpProxy: function setHttpProxy(network, info) {
    var request = this.createRequest();
    this._sendMessageForRequest("WifiManager:setHttpProxy",
                                { network: this._convertWifiNetworkToJSON(network), info:info}, request);
    return request;
  },

  setStaticIpMode: function setStaticIpMode(network, info) {
    var request = this.createRequest();
    this._sendMessageForRequest("WifiManager:setStaticIpMode",
                                { network: this._convertWifiNetworkToJSON(network), info: info}, request);
    return request;
  },

  importCert: function nsIDOMWifiManager_importCert(certBlob, certPassword, certNickname) {
    var request = this.createRequest();
    this._sendMessageForRequest("WifiManager:importCert",
                                {
                                  certBlob: certBlob,
                                  certPassword: certPassword,
                                  certNickname: certNickname
                                }, request);
    return request;
  },

  getImportedCerts: function nsIDOMWifiManager_getImportedCerts() {
    var request = this.createRequest();
    this._sendMessageForRequest("WifiManager:getImportedCerts", null, request);
    return request;
  },

  deleteCert: function nsIDOMWifiManager_deleteCert(certNickname) {
    var request = this.createRequest();
    this._sendMessageForRequest("WifiManager:deleteCert",
                                {
                                  certNickname: certNickname
                                }, request);
    return request;
  },

  get enabled() {
    return this._enabled;
  },

  get macAddress() {
    return this._macAddress;
  },

  get connection() {
    let _connection = this._convertConnection({ status: this._connectionStatus,
                                                network: this._currentNetwork,
                                              });
    return _connection;
  },

  get connectionInformation() {
    return this._lastConnectionInfo;
  },

  get capabilities() {
    return this._capabilities;
  },

  defineEventHandlerGetterSetter: function(name) {
    Object.defineProperty(this, name, {
      get: function() {
        return this.__DOM_IMPL__.getEventHandler(name);
      },
      set: function(handler) {
        this.__DOM_IMPL__.setEventHandler(name, handler);
      }
    });
  },
};

this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
  DOMWifiManager, MozWifiNetwork, MozWifiConnection, MozWifiCapabilities,
  MozWifiConnectionInfo
]);

var debug;
if (DEBUG) {
  debug = function (s) {
    dump("-*- DOMWifiManager component: " + s + "\n");
  };
} else {
  debug = function (s) {};
}