'use strict';
var cp = require('child_process');
var fs = require('fs');
var path = require('path');

var _ = require('underscore');
var request = require('request');

var h = require('./helper');
var file = require('./file');
var cache = require('./cache');
var config = require('./config');
var log = require('./log');
var Queue = require('./queue');

function Plugin(id, name, ver, desc, deps) {
  this.id = id;
  this.name = name;
  this.ver = ver || 'default';
  this.desc = desc || '';

  this.enabled = true;
  this.deleted = false;
  this.missing = (this.ver === 'missing');
  this.builtin = (this.ver === 'default');

  // only need deps for current platform
  this.deps = _.chain(deps || [])
    .filter(x => ! x.includes(':') || x.includes(':' + process.platform))
    .map(x => x.split(':')[0])
    .value();
}

Plugin.prototype.init = function() {
  this.config = config.plugins[this.name] || {};
  this.next = null;
};

Plugin.prototype.setNext = function(next) {
  Object.setPrototypeOf(this, next);
  this.next = next;
};

Plugin.prototype.delete = function() {
  if (!this.missing) {
    try {
      const fullpath = file.pluginFile(this.file);
      file.rm(fullpath);
    } catch(e) {
      return log.error(e.message);
    }
  }
  this.deleted = true;
};

Plugin.prototype.save = function() {
  const stats = cache.get(h.KEYS.plugins) || {};

  if (this.deleted) delete stats[this.name];
  else if (this.missing) return;
  else stats[this.name] = this.enabled;

  cache.set(h.KEYS.plugins, stats);
};

Plugin.prototype.install = function(cb) {
  if (this.deps.length === 0) return cb();

  const cmd = 'npm install --save ' + this.deps.join(' ');
  log.debug(cmd);
  const spin = h.spin(cmd);
  cp.exec(cmd, {cwd: file.codeDir()}, function(e) {
    spin.stop();
    return cb(e);
  });
};

Plugin.prototype.help = function() {};

Plugin.plugins = [];

Plugin.init = function(head) {
  log.trace('initializing all plugins');
  head = head || require('./core');

  const stats = cache.get(h.KEYS.plugins) || {};

  // 1. find installed plugins
  let installed = [];
  for (let f of file.listCodeDir('lib/plugins')) {
    const p = f.data;
    if (!p) continue;
    log.trace('found plugin: ' + p.name + '=' + p.ver);

    p.file = f.file;
    p.enabled = stats[p.name];

    if (!(p.name in stats)) {
      if (p.builtin) {
        log.trace('new builtin plugin, enable by default');
        p.enabled = true;
      } else {
        log.trace('new 3rd party plugin, disable by default');
        p.enabled = false;
      }
    }
    installed.push(p);
  }
  // the one with bigger `id` comes first
  installed = _.sortBy(installed, x => -x.id);

  // 2. init all in reversed order
  for (let i = installed.length - 1; i >= 0; --i) {
    const p = installed[i];
    if (p.enabled) {
      p.init();
      log.trace('inited plugin: ' + p.name);
    } else {
      log.trace('skipped plugin: ' + p.name);
    }
  }

  // 3. chain together
  const plugins = installed.filter(x => x.enabled);
  let last = head;
  for (let p of plugins) {
    last.setNext(p);
    last = p;
  }

  // 4. check missing plugins
  const missings = [];
  for (let k of _.keys(stats)) {
    if (installed.find(x => x.name === k)) continue;
    const p = new Plugin(-1, k, 'missing');
    p.enabled = stats[k];
    missings.push(p);
    log.trace('missing plugin:' + p.name);
  }

  Plugin.plugins = installed.concat(missings);
  return missings.length === 0;
};

Plugin.copy = function(src, cb) {
  // FIXME: remove local file support?
  if (path.extname(src) !== '.js') {
    src = config.sys.urls.plugin.replace('$name', src);
  }
  const dst = file.pluginFile(src);

  const srcstream = src.startsWith('https://') ? request(src) : fs.createReadStream(src);
  const dststream = fs.createWriteStream(dst);
  let error;

  srcstream.on('response', function(resp) {
    if (resp.statusCode !== 200)
      srcstream.emit('error', 'HTTP Error: ' + resp.statusCode);
  });
  srcstream.on('error', function(e) {
    dststream.emit('error', e);
  });

  dststream.on('error', function(e) {
    error = e;
    dststream.end();
  });
  dststream.on('close', function() {
    spin.stop();
    if (error) file.rm(dst);
    return cb(error, dst);
  });

  log.debug('copying from ' + src);
  const spin = h.spin('Downloading ' + src);
  srcstream.pipe(dststream);
};

Plugin.install = function(name, cb) {
  Plugin.copy(name, function(e, fullpath) {
    if (e) return cb(e);
    log.debug('copied to ' + fullpath);

    const p = require(fullpath);
    p.file = path.basename(fullpath);
    p.install(function() {
      return cb(null, p);
    });
  });
};

Plugin.installMissings = function(cb) {
  function doTask(plugin, queue, cb) {
    Plugin.install(plugin.name, function(e, p) {
      if (!e) {
        p.enabled = plugin.enabled;
        p.save();
        p.help();
      }
      return cb(e, p);
    });
  }

  const missings = Plugin.plugins.filter(x => x.missing);
  if (missings.length === 0) return cb();

  log.warn('Installing missing plugins, might take a while ...');
  const q = new Queue(missings, {}, doTask);
  q.run(1, cb);
};

Plugin.save = function() {
  for (let p of this.plugins) p.save();
};

module.exports = Plugin;