angular
.module('webui.ctrls.download', [
  'webui.services.utils', 'webui.services.rpc', 'webui.services.alerts',
  'webui.services.settings', 'webui.services.modals'
])
.controller('DownloadCtrl', [
  '$scope', '$rpc', '$utils', '$alerts', '$modals',
  '$fileSettings', '$activeInclude', '$waitingExclude',
  // for document title
  '$window',
function(
  scope, rpc, utils, alerts, modals,
  fsettings, activeInclude, waitingExclude, window
) {
  scope.active = [], scope.waiting = [], scope.stopped = [];
  scope.gstats = {};

  // pause the download
  // d: the download ctx
  scope.pause = function(d) {
    rpc.once('forcePause', [d.gid]);
  }

  // resume the download
  // d: the download ctx
  scope.resume = function(d) {
    rpc.once('unpause', [d.gid]);
  }

  // remove the download,
  // put it in stopped list if active,
  // otherwise permanantly remove it
  // d: the download ctx
  scope.remove = function(d, cb) {
    var method = 'remove';

    if (scope.getType(d) == 'stopped')
      method = 'removeDownloadResult';

    rpc.once(method, [d.gid], cb);

    // also remove it from client cache assuming that it will be deleted in the aria2 list,
    // but we could be wrong but the cache will update in next global update
    var downloads = [scope.active, scope.waiting, scope.stopped], ind = -1, i;
    for (i = 0; i < downloads.length; i++) {
      ind = downloads[i].indexOf(d);
      if (ind != -1) break;
    }

    if (ind == -1) {
      return;
    }

    downloads[i].splice(ind, 1);
  }

  scope.restart = function(d) {
    var uris =
      _.chain(d.files).map(function(f) { return f.uris })
      .filter(function(uris) { return uris.length })
      .map(function(uris) {
        return _.chain(uris)
          .map(function(u) { return u.uri })
          .uniq().value();
      }).value();

    if (uris.length > 0) {
      scope.remove(d, function() {
        rpc.once('addUri', uris, angular.noop, true);
      });
    }
  }

  // start filling in the model of active,
  // waiting and stopped download
  rpc.subscribe('tellActive', [], function(data) {
    utils.mergeMap(data[0], scope.active, scope.getCtx);
  });

  rpc.subscribe('tellWaiting', [0, 1000], function(data) {
    utils.mergeMap(data[0], scope.waiting, scope.getCtx);
  });


  rpc.subscribe('tellStopped', [0, 1000], function(data) {
    utils.mergeMap(data[0], scope.stopped, scope.getCtx);
  });

  rpc.subscribe('getGlobalStat', [], function(data) {
    scope.gstats = data[0];
    window.document.title = utils.getTitle(scope.gstats);

  });

  rpc.once('getVersion', [], function(data) {
    scope.miscellaneous = data[0];
  });

  // total number of downloads, updates dynamically as downloads are
  // stored in scope
  scope.totalDownloads = 0;

  // download search filter
  scope.downloadFilter = "";

  scope.filterDownloads = function(downloads) {
    var filter = scope.downloadFilter;
    if (!filter.length) return downloads;
    return _.filter(downloads, function(d) {
      if (!d.files.length) return true;

      return _.filter(d.files, function(f) {
        return f.path.toLowerCase().indexOf(filter.toLowerCase()) != -1;
        // return f.path.search(filter) != -1;
      }).length;
    });
  };

  // max downloads shown in one page
  scope.pageSize = 10;

  // current displayed page
  scope.currentPage = 1;

  scope.pageControlRadius = 3;

  // total maximum pages
  scope.totalPages = 0;

  // total amount of downloads returned by aria2
  scope.totalAria2Downloads = function() {
    return scope.active.length
      + scope.waiting.length
      + scope.stopped.length;
  }

  // actual downloads used by the view
  scope.getDownloads = function() {
    var downloads =
      scope.filterDownloads(
        scope.active.concat( scope.waiting ).concat( scope.stopped )
      )
    ;

    scope.totalDownloads = downloads.length;

    scope.totalPages = Math.ceil(scope.totalDownloads / scope.pageSize) || 1;

    // fix the bug when downloads are deleted until no left on a specific page
    if (scope.currentPage > scope.totalPages)
      scope.currentPage = scope.totalPages;

    downloads = downloads.slice( (scope.currentPage - 1) * scope.pageSize );
    downloads.splice( scope.pageSize );

    return downloads;
  }

  scope.setPage = function(pageNumber) {
    scope.currentPage = pageNumber;
    return false;
  }

  // get the pages to be displayed
  scope.getPages = function() {
    var minPage = scope.currentPage - scope.pageControlRadius;

    if (minPage < 1) minPage = 1;

    var maxPage = scope.currentPage + scope.pageControlRadius;

    if (maxPage > scope.totalPages)
      maxPage = scope.totalPages;

    return _.range(minPage, maxPage + 1);
  }

  // convert the donwload form aria2 to once used by the view,
  // minor additions of some fields and checks
  scope.getCtx = function(d, ctx) {
    ctx = ctx || {};

    _.each([
      'totalLength', 'completedLength', 'uploadLength', 'dir',
      'pieceLength', 'downloadSpeed', 'uploadSpeed', 'status',
      'gid', 'numPieces', 'connections', 'bitfield'
    ], function(e) {
      ctx[e] = d[e];
    });

    var files = d["files"];
    if (files) {
      var cfiles = ctx["files"] || (ctx["files"] = []);
      for (var i = 0; i < files.length; ++i) {
        var file = cfiles[i] || (cfiles[i] = {});
        file.path = files[i].path;
        file.length = files[i].length;
      }
      cfiles.length = files.length;
    }
    else {
      delete ctx["files"];
    }

    if (d.bittorrent) {
      ctx.bittorrentName = d.bittorrent.info && d.bittorrent.info.name;
      ctx.bittorrent = true;
    }
    else {
      delete ctx.bittorrentName;
    }

    // collapse the download details initially
    if (ctx.collapsed === undefined)
      ctx.collapsed = true;

    return ctx;
  };

  scope.canRestart = function(d) {
    if (['active', 'paused'].indexOf(d.status) == -1
        && !d.bittorrent)
      return true;

    return false;
  };

  scope.hasStatus = function hasStatus(d, status) {
    if (_.isArray(status)) {
      if (status.length == 0) return false;
      return hasStatus(d, status[0]) || hasStatus(d, status.slice(1));
    }
    else {
      return d.status == status;
    }
  };

  // get time left for the download with
  // current download speed,
  // could be smarter by different heuristics
  scope.getEta = function(d) {
    return (d.totalLength-d.completedLength) / d.downloadSpeed;
  }

  scope.getProgressClass = function(d) {
    switch (d.status) {
      case "paused":
        return "progress-info";
      case "error":
        return "progress-danger";
      case "removed":
        return "progress-warning";
      case "active":
        return "progress-active";
      case "complete":
        return "progress-success";
      default:
        return "";
    }
  };

  // gets the progress in percentages
  scope.getProgress = function(d) {
    var percentage = (d.completedLength / d.totalLength)*100 || 0;
    percentage = percentage.toFixed(2);
    if(!percentage) percentage = 0;

    return percentage;
  };

  // gets a pretty name for the download
  scope.getName = function(d) {
    if (d.bittorrentName) {
      return d.bittorrentName;
    }

    return utils.getFileName(
      d.files[0].path || d.files[0].uris[0].uri
    );
  }

  // gets the type for the download as classified by the aria2 rpc calls
  scope.getType = function(d) {
    var type = d.status;
    if (type == "paused") type = "waiting";
    if (["error", "removed", "complete"].indexOf(type) != -1)
      type = "stopped";
    return type;
  };

  scope.showSettings = function(d) {
    var type = scope.getType(d)
      , settings = {};

    rpc.once('getOption', [d.gid], function(data) {
      var vals = data[0];

      for (var i in fsettings) {
        if (type == 'active' && activeInclude.indexOf(i) == -1) continue;

        if (type == 'waiting' && waitingExclude.indexOf(i) != -1) continue;

        settings[i] = fsettings[i];
        settings[i].val = vals[i] || settings[i].val;
      }

      modals.invoke('settings', settings, scope.getName(d) + ' settings', function(settings) {
        var sets = {};
        for (var i in settings) { sets[i] = settings[i].val };

        rpc.once('changeOption', [d.gid, sets]);
      });
    });

    return false;
  }

}]);