#!/usr/bin/env node
'use strict';

var _commander = require('commander');

var _commander2 = _interopRequireDefault(_commander);

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

var _api = require('./api');

var _api2 = _interopRequireDefault(_api);

var _getStdin = require('get-stdin');

var _getStdin2 = _interopRequireDefault(_getStdin);

var _chalk = require('chalk');

var _chalk2 = _interopRequireDefault(_chalk);

var _tempfile = require('tempfile');

var _tempfile2 = _interopRequireDefault(_tempfile);

var _child_process = require('child_process');

var _semver = require('semver');

var _semver2 = _interopRequireDefault(_semver);

var _path = require('path');

var _path2 = _interopRequireDefault(_path);

var _elasticdump = require('elasticdump');

var _lodash = require('lodash');

var _lodash2 = _interopRequireDefault(_lodash);

var _promptPromise = require('prompt-promise');

var _promptPromise2 = _interopRequireDefault(_promptPromise);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/* global __dirname, process */

var server = '';
var defaultElastic = ' Defaults to BUGGY_COMPONENT_LIBRARY_HOST';

var log = function log() {
  if (!_commander2.default.silent) {
    var _console$log;

    for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }

    (_console$log = console.log).call.apply(_console$log, [console.log].concat(args));
  }
};

var edit = function edit(file) {
  return new Promise(function (resolve, reject) {
    var editorCmd = process.env.EDITOR || 'nano';
    var editor = (0, _child_process.spawn)(editorCmd, [file], { stdio: 'inherit' });
    editor.on('exit', function () {
      _fs2.default.readFile(file, 'utf8', function (err, contents) {
        if (err) {
          reject(err);
        } else {
          resolve(contents);
        }
      });
    });
  });
};

var editContent = function editContent(initContent, filetype, verify) {
  var tmpFile = (0, _tempfile2.default)(filetype);
  _fs2.default.writeFileSync(tmpFile, initContent, 'utf8');
  return edit(tmpFile).then(function (content) {
    if (!verify || verify(content)) {
      _fs2.default.unlinkSync(tmpFile);
      return content;
    } else {
      return (0, _promptPromise2.default)('You entered a not valid document. Do you want to continue (c), reset (r) or abort (a): ').then(function (res) {
        process.stdin.pause();
        if (res === 'c') {
          return editContent(content, filetype, verify);
        } else if (res === 'r') {
          return editContent(initContent, filetype, verify);
        } else {
          throw new Error('User aborted editing.');
        }
      });
    }
  });
};

var emptyNode = { id: '', version: '', atomic: '' };

var format = function format(obj) {
  return JSON.stringify(obj, null, 2);
};

var verifyNode = function verifyNode(node, client) {
  if (typeof node === 'string') {
    try {
      node = JSON.parse(node);
    } catch (err) {
      console.error('Could not parse JSON document');
      return false;
    }
  }
  if (!_semver2.default.valid(node.version)) {
    console.error('Invalid version given ', node.version);
    return false;
  } else {
    return client.list(node).then(function (nodeList) {
      if (_lodash2.default.find(nodeList, function (n) {
        return n.eq(n.version, node.version);
      })) {
        console.error('Version of node "' + node.id + '" already exists ', node.version);
        return false;
      } else {
        return true;
      }
    });
  }
};

var updateNode = function updateNode(node, client) {
  return new Promise(function (resolve) {
    client.get(node).then(function (content) {
      content.version = '<update version: ' + content.version + '>';
      resolve(format(content));
    }).catch(function () {
      resolve(format(_lodash2.default.merge({}, emptyNode, { id: node })));
    });
  }).then(function (initContent) {
    var verify = _lodash2.default.partial(verifyNode, _lodash2.default, client);
    return stdinOrEdit('.json', initContent, verify);
  });
};

var updateCode = function updateCode(node, version, language, client) {
  return versionOrLatest(node, version, client).then(function (version) {
    return new Promise(function (resolve) {
      return client.getCode(node, version, language).then(function (code) {
        return resolve(code);
      }).catch(function () {
        resolve('');
      });
    }).then(function (code) {
      if (code === 'undefined') {
        code = '';
      }
      return stdinOrEdit(function () {
        return client.getConfig('language', language);
      }, code).then(function (new_code) {
        if (code !== new_code) {
          client.setCode(node, version, language, new_code);
          return 1; // tell the caller that a change was made
        } else {
            return 0; // tell the caller that no change was made
          }
      });
    });
  });
};

var stdinOrEdit = function stdinOrEdit(getFiletype, content, verify) {
  if (process.stdin.isTTY) {
    log('no stdin input starting editor');
    var ft;
    if (typeof getFiletype !== 'function') {
      ft = Promise.resolve(getFiletype);
    } else {
      ft = getFiletype();
    }
    return ft.then(function (filetype) {
      return editContent(content, filetype, verify);
    });
  } else {
    // we got something on stdin, don't open the editor
    return (0, _getStdin2.default)();
  }
};

var versionOrLatest = function versionOrLatest(node, version, client) {
  if (version) {
    if (_semver2.default.valid(version)) {
      return Promise.resolve(_semver2.default.clean(version));
    } else {
      return Promise.reject('Invalid version given for ' + node + '@' + version);
    }
  } else if (node.indexOf('@') !== -1) {
    var nodeVersion = node.split('@')[1];
    if (node.split('@').length !== 2) {
      return Promise.reject('Invalid node name. Can only contain one version seperator (@) in: ' + node);
    } else if (!_semver2.default.valid(nodeVersion)) {
      return Promise.reject('Invalid version for node ' + node);
    } else {
      return Promise.resolve(_semver2.default.clean(nodeVersion));
    }
  } else {
    return client.getLatestVersion(node).then(function (version) {
      log('No valid version given using latest ' + node + '@' + version);
      return version;
    });
  }
};

if (process.env.BUGGY_COMPONENT_LIBRARY_HOST) {
  server = process.env.BUGGY_COMPONENT_LIBRARY_HOST;
  defaultElastic += '=' + server;
} else {
  server = 'http://localhost:9200';
  defaultElastic += ' or if not set to http://localhost:9200';
}

_commander2.default.version(JSON.parse(_fs2.default.readFileSync(__dirname + '/../package.json'))['version']).option('-e, --elastic <host>', 'The elastic server to connect to.' + defaultElastic, String, server).option('-p, --prefix <prefix>', 'Prefixes the database indices.', String, '').option('-n, --nice', 'Pretty print all JSON output').option('-s, --silent', 'Only print data no further information.').parse(process.argv);

function printJSON(json) {
  if (_commander2.default.nice) {
    console.log(JSON.stringify(json, null, 2));
  } else {
    console.log(JSON.stringify(json));
  }
}

_commander2.default.command('query <name>').description('Query detailed information for a specific component. This command can be used to search nodes.').action(function (name) {
  var client = (0, _api2.default)(_commander2.default.elastic, _commander2.default.prefix);
  client.query(name).then(function (node) {
    printJSON(node);
  }).catch(function (err) {
    console.error(_chalk2.default.red(err.message));
    process.exit(-1);
  });
});

_commander2.default.command('list').option('-n, --limit <limit>', 'The number of components to list.', Number, 50).option('-o, --offset <offset>', 'The number of components that should be skipped.', Number, 0).option('-h, --human-readable', 'Print the output in human-readable format.').description('Get a list of all components.').action(function (options) {
  var client = (0, _api2.default)(_commander2.default.elastic, _commander2.default.prefix);
  client.queryAll({ limit: options.limit, offset: options.offset }).then(function (nodes) {
    if (options.humanReadable) {
      nodes.forEach(function (n) {
        return console.log(n.id + '@' + n.version);
      });
    } else {
      printJSON(nodes);
    }
  }).catch(function (err) {
    console.error(_chalk2.default.red(err.message));
    process.exit(-1);
  });
});

_commander2.default.command('get <node-id> [version]').description('Get a node document by the id of the node. If the version is not specified it prints automatically the latest version').action(function (nodeID, version) {
  var client = (0, _api2.default)(_commander2.default.elastic, _commander2.default.prefix);
  versionOrLatest(nodeID, version, client).then(function (version) {
    return client.get(nodeID, version);
  }).then(function (node) {
    printJSON(node);
  }).catch(function (err) {
    console.err(_chalk2.default.red(err));
    process.exit(-1);
  });
});

_commander2.default.command('insert').description('Add a node to the component library. It opens an editor (env EDITOR) window or you can pipe the node into it.').action(function () {
  var client = (0, _api2.default)(_commander2.default.elastic, _commander2.default.prefix);
  stdinOrEdit('.json', format(emptyNode), _lodash2.default.partial(verifyNode, _lodash2.default, client)).then(function (content) {
    var node = JSON.parse(content);
    return client.insert(node).then(function () {
      return node;
    });
  }).then(function (node) {
    log(_chalk2.default.bgGreen('Successfully stored node with id: ' + node.id + '@' + node.version));
  }).catch(function (err) {
    console.error(_chalk2.default.red(err.message));
    process.exit(-1);
  });
});

_commander2.default.command('update <node-id>').description('Add a node to the component library. It opens an editor (env EDITOR) window or you can pipe the node into it.').action(function (node) {
  var client = (0, _api2.default)(_commander2.default.elastic, _commander2.default.prefix);
  updateNode(node, client).then(function (content) {
    var node = JSON.parse(content);
    return client.insert(node).then(function () {
      return node;
    });
  }).then(function (node) {
    log(_chalk2.default.bgGreen('Successfully stored node with id: ' + node.id + '@' + node.version));
  }).catch(function (err) {
    console.error(_chalk2.default.red(err.message));
    process.exit(-1);
  });
});

_commander2.default.command('set-code <node-id> <language> [version]').description('Add set code for a node in a specific programming language. It opens an editor (env EDITOR) window or you can pipe the code into it.').action(function (node, language, version) {
  var client = (0, _api2.default)(_commander2.default.elastic, _commander2.default.prefix);
  updateCode(node, version, language, client).then(function (changes_were_made) {
    if (changes_were_made) {
      log(_chalk2.default.bgGreen('Successfully stored code for node: ' + node));
    } else {
      log(_chalk2.default.bgGreen('No changes were made.'));
    }
  }).catch(function (err) {
    console.error(_chalk2.default.red(err.message));
    process.exit(-1);
  });
});

_commander2.default.command('get-code <node-id> <language> [version]').description('Get the implementation of a node in the specified language').action(function (node, language, version) {
  var client = (0, _api2.default)(_commander2.default.elastic, _commander2.default.prefix);
  versionOrLatest(node, version, client).then(function (nodeVersion) {
    return client.getCode(node, nodeVersion, language);
  }).then(function (code) {
    log('Implementation of "' + node + '" in "' + language + '"');
    console.log(code);
  }).catch(function (err) {
    console.error(_chalk2.default.red(err.message));
    process.exit(-1);
  });
});

_commander2.default.command('set-meta <node-id> <key> [version]').description('Set the meta information (as json) for a node for a specific key. It opens an editor (env EDITOR) window or you can pipe the json document into it. If the version is not specified it sets the meta information on the latest version.').action(function (node, key, version) {
  var client = (0, _api2.default)(_commander2.default.elastic, _commander2.default.prefix);
  stdinOrEdit('.json').then(function (content) {
    var data;
    try {
      data = JSON.parse(content);
    } catch (err) {
      throw new Error('Unable parse JSON data. Please provide a JSON document.');
    }
    return versionOrLatest(node, version, client).then(function (nodeVersion) {
      return client.setMeta(node, nodeVersion, key, data);
    });
  }).then(function () {
    return console.log('Successfully change meta key "' + key + '" of "' + node + '"');
  }).catch(function (err) {
    console.error(_chalk2.default.red(err.message));
    process.exit(-1);
  });
});

_commander2.default.command('get-meta <node-id> [key] [version]').option('-k, --key <key>', 'The meta key to query. If you don\'t provide a key it will print all meta information for the node').option('-v, --version <nodeVersion>', 'The version of the node. If the version is not specified it sets the meta information on the latest version.').description('Get the meta information for a node by id.').action(function (node, key, version, options) {
  var client = (0, _api2.default)(_commander2.default.elastic, _commander2.default.prefix);
  key = key || options.key;
  version = version || options.nodeVersion;
  versionOrLatest(node, version, client).then(function (nodeVersion) {
    if (key) {
      return client.getMeta(node, nodeVersion, key);
    } else {
      return client.getAllMeta(node, nodeVersion);
    }
  }).then(function (data) {
    return printJSON(data);
  }).catch(function (err) {
    console.error(_chalk2.default.red(err.message));
    process.exit(-1);
  });
});

_commander2.default.command('set-config <type> <config> <value>').description('Set the configuration `config` in the category `type` to `value`.').action(function (type, config, value) {
  var client = (0, _api2.default)(_commander2.default.elastic, _commander2.default.prefix);
  client.setConfig(type, config, value).then(function () {
    return console.log('Successfully change config "' + config + '" of type "' + type + '"');
  }).catch(function (err) {
    console.error(_chalk2.default.red(err.message));
    process.exit(-1);
  });
});

_commander2.default.command('get-config <type> <config>').description('Get the configuration `config` in the category `type`.').action(function (type, config) {
  var client = (0, _api2.default)(_commander2.default.elastic, _commander2.default.prefix);
  client.getConfig(type, config).then(function (data) {
    return console.log(data);
  }).catch(function (err) {
    console.error(_chalk2.default.red(err.message));
    process.exit(-1);
  });
});

var dumpOptions = {
  all: false, limit: 100, offset: 0, debug: false, type: 'data', delete: false, bulk: false, maxSockets: null,
  'input-index': null, 'output-index': null, inputTransport: null, outputTransport: null, searchBody: null,
  sourceOnly: false, jsonLines: false, format: '', 'ignore-errors': false, scrollTime: '10m',
  'bulk-use-output-index-name': false, 'bulk-mode': 'index', timeout: null, skip: null, toLog: null
};

_commander2.default.command('export <outfile>').description('Export the whole component library into one file. You can either specify a plain .json document or a .gz archive.').action(function (outfile, options) {
  var exportOptions = _lodash2.default.merge({}, dumpOptions, {
    all: true,
    input: _commander2.default.elastic,
    output: outfile
  });
  if (_path2.default.extname(outfile) === '.json') {
    // create json export
    var dumper = new _elasticdump.elasticdump(options.input, options.output, exportOptions);
    dumper.on('log', function (message) {
      console.log('log', message);
    });
    dumper.on('debug', function (message) {
      console.log('debug', message);
    });
    dumper.on('error', function (error) {
      console.error('log', 'Error Emitted => ' + (error.message || JSON.stringify(error)));
    });

    dumper.dump(function (error, total_writes) {
      console.log('done dumpnig', error);
      if (error) {
        process.exit(1);
      } else {
        process.exit(0);
      }
    });
  } else if (_path2.default.extname(outfile) === '.gz') {
    // TODO: pipe through zlib
  }
});

_commander2.default.command('import <infile>').description('Import a backup into the elasticsearch database').action(function (infile, options) {
  var exportOptions = _lodash2.default.merge({}, dumpOptions, {
    bulk: true,
    input: infile,
    output: _commander2.default.elastic
  });
  if (_path2.default.extname(infile) === '.json') {
    // create json export
    var dumper = new _elasticdump.elasticdump(options.input, options.output, exportOptions);
    dumper.on('log', function (message) {
      console.log('log', message);
    });
    dumper.on('debug', function (message) {
      console.log('debug', message);
    });
    dumper.on('error', function (error) {
      console.error('log', 'Error Emitted => ' + (error.message || JSON.stringify(error)));
    });

    dumper.dump(function (error, total_writes) {
      console.log('done dumpnig', error);
      if (error) {
        process.exit(1);
      } else {
        process.exit(0);
      }
    });
  } else if (_path2.default.extname(infile) === '.gz') {
    // TODO: pipe through zlib
  }
});

_commander2.default.parse(process.argv);