/* -*- 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;

const CONNECTION_STATUS_DISCONNECTED  = "disconnected";
const CONNECTION_STATUS_CONNECTING    = "connecting";
const CONNECTION_STATUS_CONNECTED     = "connected";
const CONNECTION_STATUS_DISCONNECTING = "disconnecting";

const DEBUG = false;

this.EXPORTED_SYMBOLS = ["WifiP2pWorkerObserver"];

// WifiP2pWorkerObserver resides in WifiWorker to handle DOM message
// by either 1) returning internally maintained information or
//           2) delegating to aDomMsgResponder. It is also responsible
// for observing events from WifiP2pManager and dispatch to DOM.
//
// @param aDomMsgResponder handles DOM messages, including
//        - setScanEnabled
//        - connect
//        - disconnect
//        - setPairingConfirmation
//        The instance is actually WifiP2pManager.
this.WifiP2pWorkerObserver = function(aDomMsgResponder) {
  function debug(aMsg) {
    if (DEBUG) {
      dump('-------------- WifiP2pWorkerObserver: ' + aMsg);
    }
  }

  // Private member variables.
  let _localDevice;
  let _peerList = {}; // List of P2pDevice.
  let _domManagers = [];
  let _enabled = false;
  let _groupOwner = null;
  let _currentPeer = null;

  // Constructor of P2pDevice. It will be exposed to DOM.
  //
  // @param aPeer object representing a P2P device:
  //   .name: string for the device name.
  //   .address: Mac address.
  //   .isGroupOwner: boolean to indicate if this device is the group owner.
  //   .wpsCapabilities: array of string of {"pbc", "display", "keypad"}.
  function P2pDevice(aPeer) {
    this.address = aPeer.address;
    this.name = (aPeer.name ? aPeer.name : aPeer.address);
    this.isGroupOwner = aPeer.isGroupOwner;
    this.wpsCapabilities = aPeer.wpsCapabilities;
    this.connectionStatus = CONNECTION_STATUS_DISCONNECTED;

    // Since this object will be exposed to web, defined the exposed
    // properties here.
    this.__exposedProps__ = {
      address: "r",
      name: "r",
      isGroupOwner: "r",
      wpsCapabilities: "r",
      connectionStatus: "r"
    };
  }

  // Constructor of P2pGroupOwner.
  //
  // @param aGroupOwner:
  //   .macAddress
  //   .ipAddress
  //   .passphrase
  //   .ssid
  //   .freq
  //   .isLocal
  function P2pGroupOwner(aGroupOwner) {
    this.macAddress = aGroupOwner.macAddress; // The identifier to get further information.
    this.ipAddress = aGroupOwner.ipAddress;
    this.passphrase = aGroupOwner.passphrase;
    this.ssid = aGroupOwner.ssid; // e.g. DIRECT-xy.
    this.freq = aGroupOwner.freq;
    this.isLocal = aGroupOwner.isLocal;

    let detail = _peerList[aGroupOwner.macAddress];
    if (detail) {
      this.name = detail.name;
      this.wpsCapabilities = detail.wpsCapabilities;
    } else if (_localDevice.address === this.macAddress) {
      this.name = _localDevice.name;
      this.wpsCapabilities = _localDevice.wpsCapabilities;
    } else {
      debug("We don't know this group owner: " + aGroupOwner.macAddress);
      this.name = aGroupOwner.macAddress;
      this.wpsCapabilities = [];
    }
  }

  function fireEvent(aMessage, aData) {
    debug('domManager: ' + JSON.stringify(_domManagers));
    _domManagers.forEach(function(manager) {
      // Note: We should never have a dead message manager here because we
      // observe our child message managers shutting down below.
      manager.sendAsyncMessage("WifiP2pManager:" + aMessage, aData);
    });
  }

  function addDomManager(aMsg) {
    if (-1 === _domManagers.indexOf(aMsg.manager)) {
      _domManagers.push(aMsg.manager);
    }
  }

  function returnMessage(aMessage, aSuccess, aData, aMsg) {
    let rMsg = aMessage + ":Return:" + (aSuccess ? "OK" : "NO");
    aMsg.manager.sendAsyncMessage(rMsg,
                                 { data: aData, rid: aMsg.rid, mid: aMsg.mid });
  }

  function handlePeerListUpdated() {
    fireEvent("onpeerinfoupdate", {});
  }

  // Return a literal object as the constructed object.
  return {
    onLocalDeviceChanged: function(aDevice) {
      _localDevice = aDevice;
      debug('Local device updated to: ' + JSON.stringify(_localDevice));
    },

    onEnabled: function() {
      _peerList = [];
      _enabled = true;
      fireEvent("p2pUp", {});
    },

    onDisbaled: function() {
      _enabled = false;
      fireEvent("p2pDown", {});
    },

    onPeerFound: function(aPeer) {
      let newFoundPeer = new P2pDevice(aPeer);
      let origianlPeer = _peerList[aPeer.address];
      _peerList[aPeer.address] = newFoundPeer;
      if (origianlPeer) {
        newFoundPeer.connectionStatus = origianlPeer.connectionStatus;
      }
      handlePeerListUpdated();
    },

    onPeerLost: function(aPeer) {
      let lostPeer = _peerList[aPeer.address];
      if (!lostPeer) {
        debug('Unknown peer lost: ' + aPeer.address);
        return;
      }
      delete _peerList[aPeer.address];
      handlePeerListUpdated();
    },

    onConnecting: function(aPeer) {
      let peer = _peerList[aPeer.address];
      if (!peer) {
        debug('Unknown peer connecting: ' + aPeer.address);
        peer = new P2pDevice(aPeer);
        _peerList[aPeer.address] = peer;
        handlePeerListUpdated();
      }
      peer.connectionStatus = CONNECTION_STATUS_CONNECTING;

      fireEvent('onconnecting', { peer: peer });
    },

    onConnected: function(aGroupOwner, aPeer) {
      let go = new P2pGroupOwner(aGroupOwner);
      let peer = _peerList[aPeer.address];
      if (!peer) {
        debug('Unknown peer connected: ' + aPeer.address);
        peer = new P2pDevice(aPeer);
        _peerList[aPeer.address] = peer;
        handlePeerListUpdated();
      }
      peer.connectionStatus = CONNECTION_STATUS_CONNECTED;
      peer.isGroupOwner = (aPeer.address === aGroupOwner.address);

      _groupOwner = go;
      _currentPeer = peer;

      fireEvent('onconnected', { groupOwner: go, peer: peer });
    },

    onDisconnected: function(aPeer) {
      let peer = _peerList[aPeer.address];
      if (!peer) {
        debug('Unknown peer disconnected: ' + aPeer.address);
        return;
      }

      peer.connectionStatus = CONNECTION_STATUS_DISCONNECTED;

      _groupOwner = null;
      _currentPeer = null;

      fireEvent('ondisconnected', { peer: peer });
    },

    getObservedDOMMessages: function() {
      return [
        "WifiP2pManager:getState",
        "WifiP2pManager:getPeerList",
        "WifiP2pManager:setScanEnabled",
        "WifiP2pManager:connect",
        "WifiP2pManager:disconnect",
        "WifiP2pManager:setPairingConfirmation",
        "WifiP2pManager:setDeviceName"
      ];
    },

    onDOMMessage: function(aMessage) {
      let msg = aMessage.data || {};
      msg.manager = aMessage.target;

      if ("child-process-shutdown" === aMessage.name) {
        let i;
        if (-1 !== (i = _domManagers.indexOf(msg.manager))) {
          _domManagers.splice(i, 1);
        }
        return;
      }

      if (!aMessage.target.assertPermission("wifi-manage")) {
        return;
      }

      switch (aMessage.name) {
        case "WifiP2pManager:getState": // A new DOM manager is created.
          addDomManager(msg);
          return { // Synchronous call. Simply return it.
            enabled: _enabled,
            groupOwner: _groupOwner,
            currentPeer: _currentPeer
          };

        case "WifiP2pManager:setScanEnabled":
          {
            let enabled = msg.data;

            aDomMsgResponder.setScanEnabled(enabled, function(success) {
              returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
            });
          }
          break;

        case "WifiP2pManager:getPeerList":
          {
            // Convert the object to an array.
            let peerArray = [];
            for (let key in _peerList) {
              if (_peerList.hasOwnProperty(key)) {
                peerArray.push(_peerList[key]);
              }
            }

            returnMessage(aMessage.name, true, peerArray, msg);
          }
          break;

        case "WifiP2pManager:connect":
          {
            let peer = msg.data;

            let onDoConnect = function(success) {
              returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
            };

            aDomMsgResponder.connect(peer.address, peer.wpsMethod,
                                     peer.goIntent, onDoConnect);
          }
          break;

        case "WifiP2pManager:disconnect":
          {
            let address = msg.data;

            aDomMsgResponder.disconnect(address, function(success) {
              returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
            });
          }
          break;

        case "WifiP2pManager:setPairingConfirmation":
          {
            let result = msg.data;
            aDomMsgResponder.setPairingConfirmation(result);
            returnMessage(aMessage.name, true, true, msg);
          }
          break;

        case "WifiP2pManager:setDeviceName":
          {
            let newDeviceName = msg.data;
            aDomMsgResponder.setDeviceName(newDeviceName, function(success) {
              returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
            });
          }
          break;

        default:
          if (0 === aMessage.name.indexOf("WifiP2pManager:")) {
            debug("DOM WifiP2pManager message not handled: " + aMessage.name);
          }
      } // End of switch.
    }
  };
};