/**
 * Shell数据库管理模块
 * 更新：2016/06/28
 */

'use strict';

const fs = require('fs'),
  dns = require('dns'),
  path = require('path'),
  CONF = require('./config'),
  Datastore = require('nedb'),
  qqwry = require("geoips").info();

var logger;

class Database {

  /**
   * 初始化数据库
   * @param  {electron} electron electron对象
   * @return {[type]}          [description]
   */
  constructor(electron) {
    logger = new electron.Logger('Database');
    this.cursor = new Datastore({
      filename: CONF.dataPath,
      autoload: true
    });
    // 监听事件
    electron.ipcMain
      .on('shell-add', this.addShell.bind(this))
      .on('shell-del', this.delShell.bind(this))
      .on('shell-edit', this.editShell.bind(this))
      .on('shell-move', this.moveShell.bind(this))
      .on('shell-find', this.findShell.bind(this))
      .on('shell-clear', this.clearShell.bind(this))
      .on('shell-findOne', this.findOneShell.bind(this))
      .on('shell-addDataConf', this.addDataConf.bind(this))
      .on('shell-editDataConf', this.editDataConf.bind(this))
      .on('shell-delDataConf', this.delDataConf.bind(this))
      .on('shell-getDataConf', this.getDataConf.bind(this))
      .on('shell-renameCategory', this.renameShellCategory.bind(this))
      .on('shell-updateHttpConf', this.updateHttpConf.bind(this))
      .on('shell-addPluginDataConf', this.addPluginDataConf.bind(this))
      .on('shell-editPluginDataConf', this.editPluginDataConf.bind(this))
      .on('shell-delPluginDataConf', this.delPluginDataConf.bind(this))
      .on('shell-getPluginDataConf', this.getPluginDataConf.bind(this));
  }

  convertOptstoNedbQuery(opts={}) {
    var self = this;
    if(opts instanceof Array) {
      for (let i = 0; i < opts.length; i++) {
        opts[i] = self.convertOptstoNedbQuery(opts[i]);
      }
    }else if(opts instanceof Object) {
      Object.keys(opts).map((f) => {
        if(opts[f] instanceof Object) {
          opts[f] = self.convertOptstoNedbQuery(opts[f]);
        }
        if(f == "$regex") {
          if(opts[f].charAt(0) == '*') {
            opts[f] = opts[f].substring(1);
          }
          opts[f] = new RegExp(opts[f], 'i');
        }
      });
    }
    return opts;
  }
  /**
   * 查询shell数据
   * @param  {Object} event ipcMain对象
   * @param  {Object} opts  查询配置
   * @return {[type]}       [description]
   */
  findShell(event, opts = {}) {
    opts = this.convertOptstoNedbQuery(opts);
    logger.debug('findShell', opts);
    this.cursor
      .find(opts)
      .sort({
        utime: -1
      })
      .exec((err, ret) => {
        event.returnValue = ret || [];
      });
  }

  /**
   * 查询单一shell数据
   * @param  {Object} event ipcMain对象
   * @param  {String} opts  shell id
   * @return {[type]}       [description]
   */
  findOneShell(event, opts) {
    logger.debug('findOneShell', opts);
    this.cursor.findOne({
      _id: opts
    }, (err, ret) => {
      event.returnValue = err || ret;
    });
  }


  /**
   * 根据URL解析出IP&&地理位置
   * @param  {String} url URL地址
   * @return {Promise}     ip, addr
   */
  _url2ip(url) {
    return new Promise((res, rej) => {
      // 解析domain
      const urlArr = url.match(/(\w+):\/\/([\w\.\-]+)[:]?([\d]*)([\s\S]*)/i);
      // 无效url
      if (!urlArr || urlArr.length < 3) {
        return rej('Unable to resolve domain name from URL');
      }
      // 获取IP
      const domain = urlArr[2];
      dns.lookup(domain, (err, ip) => {
        if (err) {
          return rej(err.toString());
        }
        // 获取地理位置
        const _addr = qqwry.searchIP(ip);
        return res({
          ip: ip,
          addr: `${_addr.Country} ${_addr.Area}`
        });
      })
    })
  }

  /**
   * 添加shell数据
   * @param {Object} event ipcMain对象
   * @param {Object} opts  数据（url,category,pwd,type,encode,encoder
   */
  addShell(event, opts) {
    logger.info('addShell', opts);
    if(opts.base['url'].match(CONF.urlblacklist)){
      event.returnValue = "Blacklist URL"
      return
    }
    this._url2ip(opts.base['url'])
      .then((ret) => {
        this.cursor.insert({
          category: opts.base['category'] || 'default',
          url: opts.base['url'],
          pwd: opts.base['pwd'],
          note: opts.base['note'],
          type: opts.base['type'],
          ip: ret['ip'],
          addr: ret['addr'],
          encode: opts.base['encode'],
          encoder: opts.base['encoder'],
          httpConf: opts.http,
          otherConf: opts.other,
          ctime: +new Date,
          utime: +new Date
        }, (_err, _ret) => {
          event.returnValue = _err || _ret;
        });
      })
      .catch((_err) => {
        event.returnValue = _err;
      })
  }

  /**
   * 编辑shell数据
   * @param  {Object} event ipcMain对象
   * @param  {Object} opts  数据（old,new
   * @return {[type]}       [description]
   */
  editShell(event, opts) {
    logger.warn('editShell', opts);

    const _new = opts.new;
    const _old = opts.old;
    if(_new.base['url'].match(CONF.urlblacklist)){
      event.returnValue = "Blacklist URL"
      return
    }
    this._url2ip(_new.base['url'])
      .then((ret) => {
        this.cursor.update({
          _id: _old['_id']
        }, {
          $set: {
            ip: ret['ip'],
            addr: ret['addr'],
            url: _new.base['url'],
            pwd: _new.base['pwd'],
            note: _new.base['note'],
            type: _new.base['type'],
            encode: _new.base['encode'],
            encoder: _new.base['encoder'],
            httpConf: _new.http,
            otherConf: _new.other,
            utime: +new Date
          }
        }, (_err, _ret) => {
          event.returnValue = _err || _ret;
        })
      })
      .catch((_err) => {
        event.returnValue = _err;
      });
  }

  /**
   * 更新httpConf配置信息（包含body&&headers
   * @param  {[type]} event [description]
   * @param  {[type]} opt  = {} [description]
   * @return {[type]}       [description]
   */
  updateHttpConf(event, opt = {}) {
    logger.warn('updateHttpConf', opt);

    this.cursor.update({
      _id: opt._id
    }, {
      $set: {
        httpConf: opt.conf,
        utime: +new Date
      }
    }, (_err, _ret) => {
      event.returnValue = _err || _ret;
    });
  }

  /**
   * 删除shell数据
   * @param  {Object} event ipcMain对象
   * @param  {Array}  opts  要删除的shell-id列表
   * @return {[type]}       [description]
   */
  delShell(event, opts) {
    logger.warn('delShell', opts);
    this.cursor.remove({
      _id: {
        $in: opts
      }
    }, {
      multi: true
    }, (err, num) => {
      event.returnValue = err || num;
    })
  }

  /**
   * 删除分类shell数据
   * @param  {Object} event ipcMain对象
   * @param  {String} opts  shell分类名
   * @return {[type]}       [description]
   */
  clearShell(event, opts) {
    logger.fatal('clearShell', opts);
    this.cursor.remove({
      category: opts
    }, {
      multi: true
    }, (err, num) => {
      event.returnValue = err || num;
    })
  }

  /**
   * 重命名shell分类
   * @param  {Object} event ipcMain对象
   * @param  {Object} opts  配置（oldName,newName
   * @return {[type]}       [description]
   */
  renameShellCategory(event, opts) {
    logger.warn('renameShellCategory', opts);
    this.cursor.update({
      category: opts['oldName']
    }, {
      $set: {
        category: opts['newName']
      }
    }, {
      multi: true
    }, (err, num) => {
      event.returnValue = err || num;
    })
  }

  /**
   * 移动shell数据分类
   * @param  {Object} event ipcMain对象
   * @param  {Object} opts  配置（ids,category
   * @return {[type]}       [description]
   */
  moveShell(event, opts) {
    logger.info('moveShell', opts);
    this.cursor.update({
      _id: {
        $in: opts['ids'] || []
      }
    }, {
      $set: {
        category: opts['category'] || 'default',
        utime: +new Date
      }
    }, {
      multi: true
    }, (err, num) => {
      event.returnValue = err || num;
    })
  }

  /**
   * 添加数据库配置
   * @param {Object} event ipcMain对象
   * @param {Object} opts  配置（_id,data
   */
  addDataConf(event, opts) {
    logger.info('addDataConf', opts);
    // 1. 获取原配置列表
    this.cursor.findOne({
      _id: opts['_id']
    }, (err, ret) => {
      let confs = ret['database'] || {};
      // 随机Id（顺序增长
      const random_id = parseInt(+new Date + Math.random() * 1000).toString(16);
      // 添加到配置
      confs[random_id] = opts['data'];
      // 更新数据库
      this.cursor.update({
        _id: opts['_id']
      }, {
        $set: {
          database: confs,
          utime: +new Date
        }
      }, (_err, _ret) => {
        event.returnValue = random_id;
      });
    });
  }

  /**
   * 修改数据库配置
   * @param {Object} event ipcMain对象
   * @param {Object} opts  配置（_id,id,data
   */
  editDataConf(event, opts) {
    logger.info('editDataConf', opts);
    // 1. 获取原配置列表
    this.cursor.findOne({
      _id: opts['_id']
    }, (err, ret) => {
      let confs = ret['database'] || {};
      // 添加到配置
      confs[opts['id']] = opts['data'];
      // 更新数据库
      this.cursor.update({
        _id: opts['_id']
      }, {
        $set: {
          database: confs,
          utime: +new Date
        }
      }, (_err, _ret) => {
        event.returnValue = opts['id'];
      });
    });
  }

  /**
   * 删除数据库配置
   * @param  {Object} event ipcMain对象
   * @param  {Object} opts  配置（_id,id
   * @return {[type]}       [description]
   */
  delDataConf(event, opts) {
    logger.info('delDataConf', opts);
    // 1. 获取原配置
    this.cursor.findOne({
      _id: opts['_id']
    }, (err, ret) => {
      let confs = ret['database'] || {};
      // 2. 删除配置
      delete confs[opts['id']];
      // 3. 更新数据库
      this.cursor.update({
        _id: opts['_id']
      }, {
        $set: {
          database: confs,
          utime: +new Date
        }
      }, (_err, _ret) => {
        event.returnValue = _err || _ret;
      });
    })
  }

  /**
   * 获取单个数据库配置
   * @param  {Object} event ipcMain对象
   * @param  {Object} opts  配置（_id,id
   * @return {[type]}       [description]
   */
  getDataConf(event, opts) {
    logger.info('getDatConf', opts);
    this.cursor.findOne({
      _id: opts['_id']
    }, (err, ret) => {
      const confs = ret['database'] || {};
      event.returnValue = err || confs[opts['id']];
    });
  }

  /**
   * 添加插件数据配置
   * @param {Object} event ipcMain对象
   * @param  {string} plugin 插件注册的名称
   * @param {Object} opts  配置（_id,data
   */
  addPluginDataConf(event, plugin, opts) {
    logger.info('addPluginDataConf', plugin, opts);
    // 1. 获取原配置列表
    this.cursor.findOne({
      _id: opts['_id']
    }, (err, ret) => {
      ret['plugins'] = ret['plugins'] || {};
      let confs = ret['plugins'][plugin] || {};
      // 随机Id（顺序增长
      const random_id = parseInt(+new Date + Math.random() * 1000).toString(16);
      // 添加到配置
      confs[random_id] = opts['data'];
      let setdata = {
        utime: +new Date,
      }
      setdata[`plugins.${plugin}`] = confs;

      // 更新
      this.cursor.update({
        _id: opts['_id']
      }, {
        $set: setdata
      }, (_err, _ret) => {
        event.returnValue = random_id;
      });
    });
  }
  /**
   * 修改插件数据配置
   * @param {Object} event ipcMain对象
   * @param  {string} plugin 插件注册的名称
   * @param {Object} opts  配置（_id,id,data
   */
  editPluginDataConf(event, plugin, opts) {
    logger.info('editPluginDataConf', plugin, opts);
    // 1. 获取原配置列表
    this.cursor.findOne({
      _id: opts['_id']
    }, (err, ret) => {
      ret['plugins'] = ret['plugins'] || {};
      let confs = ret['plugins'][plugin] || {};
      // 添加到配置
      confs[opts['id']] = opts['data'];
      let setdata = {
        utime: +new Date,
      }
      setdata[`plugins.${plugin}`] = confs;
      // 更新数据库
      this.cursor.update({
        _id: opts['_id']
      }, {
        $set: setdata
      }, (_err, _ret) => {
        event.returnValue = opts['id'];
      });
    });
  }

  /**
   * 删除插件数据配置
   * @param  {Object} event ipcMain对象
   * @param  {string} plugin 插件注册的名称
   * @param  {Object} opts  配置（_id,id
   * @return {[type]}       [description]
   */
  delPluginDataConf(event, plugin, opts) {
    logger.info('delPluginDataConf', plugin, opts);
    // 1. 获取原配置
    this.cursor.findOne({
      _id: opts['_id']
    }, (err, ret) => {
      ret['plugins'] = ret['plugins'] || {};
      let confs = ret['plugins'][plugin] || {};
      // 2. 删除配置
      delete confs[opts['id']];
      let setdata = {
        utime: +new Date,
      }
      setdata[`plugins.${plugin}`] = confs;
      // 3. 更新数据库
      this.cursor.update({
        _id: opts['_id']
      }, {
        $set: setdata
      }, (_err, _ret) => {
        event.returnValue = _err || _ret;
      });
    })
  }

  /**
   * 获取单个插件数据配置
   * @param  {Object} event ipcMain对象
   * @param  {string} plugin 插件注册的名称
   * @param  {Object} opts  配置（_id,id
   * @return {[type]}       [description]
   */
  getPluginDataConf(event, plugin, opts) {
    logger.info('getPluginDatConf', plugin, opts);
    this.cursor.findOne({
      _id: opts['_id']
    }, (err, ret) => {
      ret['plugins'] = ret['plugins'] || {};
      const confs = ret['plugins'][plugin] || {};
      event.returnValue = err || confs[opts['id']];
    });
  }
}

module.exports = Database;
