/* 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 Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

this.EXPORTED_SYMBOLS = [];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Downloads.jsm");
Cu.import("resource://gre/modules/Task.jsm");

XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                   "@mozilla.org/parentprocessmessagemanager;1",
                                   "nsIMessageBroadcaster");

function debug(aStr) {
//@line 25 "c:\builds\moz2_slave\m-b30_14-w32_g-ntly-0000000000\build\dom\downloads\src\DownloadsAPI.jsm"
}

function sendPromiseMessage(aMm, aMessageName, aData, aError) {
  debug("sendPromiseMessage " + aMessageName);
  let msg = {
    id: aData.id,
    promiseId: aData.promiseId
  };

  if (aError) {
    msg.error = aError;
  }

  aMm.sendAsyncMessage(aMessageName, msg);
}

let DownloadsAPI = {
  init: function() {
    debug("init");

    this._ids = new WeakMap(); // Maps toolkit download objects to ids.
    this._index = {};          // Maps ids to downloads.

    ["Downloads:GetList",
     "Downloads:ClearAllDone",
     "Downloads:Remove",
     "Downloads:Pause",
     "Downloads:Resume"].forEach((msgName) => {
      ppmm.addMessageListener(msgName, this);
    });

    let self = this;
    Task.spawn(function () {
      let list = yield Downloads.getList(Downloads.ALL);
      yield list.addView(self);

      debug("view added to download list.");
    }).then(null, Components.utils.reportError);

    this._currentId = 0;
  },

  /**
    * Returns a unique id for each download, hashing the url and the path.
    */
  downloadId: function(aDownload) {
    let id = this._ids.get(aDownload, null);
    if (!id) {
      id = "download-" + this._currentId++;
      this._ids.set(aDownload, id);
      this._index[id] = aDownload;
    }
    return id;
  },

  getDownloadById: function(aId) {
    return this._index[aId];
  },

  /**
    * Converts a download object into a plain json object that we'll
    * send to the DOM side.
    */
  jsonDownload: function(aDownload) {
    let res = {
      totalBytes: aDownload.totalBytes,
      currentBytes: aDownload.currentBytes,
      url: aDownload.source.url,
      path: aDownload.target.path,
      contentType: aDownload.contentType,
      startTime: aDownload.startTime.getTime()
    };

    if (aDownload.error) {
      res.error = aDownload.error;
    }

    res.id = this.downloadId(aDownload);

    // The state of the download. Can be any of "downloading", "stopped",
    // "succeeded", finalized".

    // Default to "stopped"
    res.state = "stopped";
    if (!aDownload.stopped &&
        !aDownload.canceled &&
        !aDownload.succeeded &&
        !aDownload.DownloadError) {
      res.state = "downloading";
    } else if (aDownload.succeeded) {
      res.state = "succeeded";
    }
    return res;
  },

  /**
    * download view methods.
    */
  onDownloadAdded: function(aDownload) {
    let download = this.jsonDownload(aDownload);
    debug("onDownloadAdded " + uneval(download));
    ppmm.broadcastAsyncMessage("Downloads:Added", download);
  },

  onDownloadRemoved: function(aDownload) {
    let download = this.jsonDownload(aDownload);
    download.state = "finalized";
    debug("onDownloadRemoved " + uneval(download));
    ppmm.broadcastAsyncMessage("Downloads:Removed", download);
    this._index[this._ids.get(aDownload)] = null;
    this._ids.delete(aDownload);
  },

  onDownloadChanged: function(aDownload) {
    let download = this.jsonDownload(aDownload);
    debug("onDownloadChanged " + uneval(download));
    ppmm.broadcastAsyncMessage("Downloads:Changed", download);
  },

  receiveMessage: function(aMessage) {
    if (!aMessage.target.assertPermission("downloads")) {
      debug("No 'downloads' permission!");
      return;
    }

    debug("message: " + aMessage.name);

    switch (aMessage.name) {
    case "Downloads:GetList":
      this.getList(aMessage.data, aMessage.target);
      break;
    case "Downloads:ClearAllDone":
      this.clearAllDone(aMessage.data, aMessage.target);
      break;
    case "Downloads:Remove":
      this.remove(aMessage.data, aMessage.target);
      break;
    case "Downloads:Pause":
      this.pause(aMessage.data, aMessage.target);
      break;
    case "Downloads:Resume":
      this.resume(aMessage.data, aMessage.target);
      break;
    default:
      debug("Invalid message: " + aMessage.name);
    }
  },

  getList: function(aData, aMm) {
    debug("getList called!");
    let self = this;
    Task.spawn(function () {
      let list = yield Downloads.getList(Downloads.ALL);
      let downloads = yield list.getAll();
      let res = [];
      downloads.forEach((aDownload) => {
        res.push(self.jsonDownload(aDownload));
      });
      aMm.sendAsyncMessage("Downloads:GetList:Return", res);
    }).then(null, Components.utils.reportError);
  },

  clearAllDone: function(aData, aMm) {
    debug("clearAllDone called!");
    let self = this;
    Task.spawn(function () {
      let list = yield Downloads.getList(Downloads.ALL);
      yield list.removeFinished();
      list = yield Downloads.getList(Downloads.ALL);
      let downloads = yield list.getAll();
      let res = [];
      downloads.forEach((aDownload) => {
        res.push(self.jsonDownload(aDownload));
      });
      aMm.sendAsyncMessage("Downloads:ClearAllDone:Return", res);
    }).then(null, Components.utils.reportError);
  },

  remove: function(aData, aMm) {
    debug("remove id " + aData.id);
    let download = this.getDownloadById(aData.id);
    if (!download) {
      sendPromiseMessage(aMm, "Downloads:Remove:Return",
                         aData, "NoSuchDownload");
      return;
    }

    Task.spawn(function() {
      yield download.finalize(true);
      let list = yield Downloads.getList(Downloads.ALL);
      yield list.remove(download);
    }).then(
      function() {
        sendPromiseMessage(aMm, "Downloads:Remove:Return", aData);
      },
      function() {
        sendPromiseMessage(aMm, "Downloads:Remove:Return",
                           aData, "RemoveError");
      }
    );
  },

  pause: function(aData, aMm) {
    debug("pause id " + aData.id);
    let download = this.getDownloadById(aData.id);
    if (!download) {
      sendPromiseMessage(aMm, "Downloads:Pause:Return",
                         aData, "NoSuchDownload");
      return;
    }

    download.cancel().then(
      function() {
        sendPromiseMessage(aMm, "Downloads:Pause:Return", aData);
      },
      function() {
        sendPromiseMessage(aMm, "Downloads:Pause:Return",
                           aData, "PauseError");
      }
    );
  },

  resume: function(aData, aMm) {
    debug("resume id " + aData.id);
    let download = this.getDownloadById(aData.id);
    if (!download) {
      sendPromiseMessage(aMm, "Downloads:Resume:Return",
                         aData, "NoSuchDownload");
      return;
    }

    download.start().then(
      function() {
        sendPromiseMessage(aMm, "Downloads:Resume:Return", aData);
      },
      function() {
        sendPromiseMessage(aMm, "Downloads:Resume:Return",
                           aData, "ResumeError");
      }
    );
  }
};

DownloadsAPI.init();
