'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; /* global __dirname */

exports.default = connect;

var _elasticsearch = require('elasticsearch');

var elastic = _interopRequireWildcard(_elasticsearch);

var _lodash = require('lodash');

var _lodash2 = _interopRequireDefault(_lodash);

var _semver = require('semver');

var _semver2 = _interopRequireDefault(_semver);

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

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

var extractHits = _lodash2.default.partial(_lodash2.default.get, _lodash2.default, 'hits.hits');
var getSource = _lodash2.default.partial(_lodash2.default.get, _lodash2.default, '_source');
var mapHits = _lodash2.default.partial(_lodash2.default.map, _lodash2.default, getSource);
var valid = function valid(obj) {
  if (!obj.id) {
    return { error: 'Node must have a meta-id', valid: false };
  } else if (obj.id.indexOf('@') !== -1) {
    return { error: 'Node id (' + obj.id + ') cannot have the special character @', valid: false };
  } else if (!obj.version) {
    return { error: 'Node must have an id', valid: false };
  } else if (!_semver2.default.valid(obj.version)) {
    return { error: 'Node must have a valid semver version', valid: false };
  } else {
    return { valid: true };
  }
};

var normalizeNode = function normalizeNode(node) {
  var normNode = _lodash2.default.clone(node);
  normNode.version = _semver2.default.clean(node.version);
  return normNode;
};

var clearIndex = function clearIndex(api, client, index) {
  return api.flush().then(function () {
    return client.search({ index: index, q: '*' });
  }).then(function (v) {
    v.hits.hits.forEach(function (v) {
      client.delete({ index: index, type: v._type, id: v._id });
    });
  }).then(function () {
    return api.flush();
  });
};

function connect(host) {
  var prefix = arguments.length <= 1 || arguments[1] === undefined ? '' : arguments[1];

  var client = new elastic.Client({
    host: host
  });

  if (prefix.length !== 0) {
    prefix += '_';
  }
  var nodesIndex = prefix + 'nodes';
  var metaIndex = prefix + 'meta';
  var configIndex = prefix + 'configuration';
  var indices = [nodesIndex, metaIndex, configIndex];

  var api = {
    isConnected: function isConnected() {
      return client.ping();
    },

    query: function query(id) {
      return client.search({
        index: nodesIndex,
        body: {
          query: {
            match: {
              id: id
            }
          }
        }
      }).then(extractHits).then(mapHits);
    },

    queryAll: function queryAll(_ref) {
      var limit = _ref.limit;
      var offset = _ref.offset;

      return client.search({
        index: nodesIndex,
        size: limit,
        from: offset,
        body: {
          query: {
            match_all: {}
          }
        }
      }).then(extractHits).then(mapHits);
    },

    list: function list(id) {
      return client.search({
        index: nodesIndex,
        type: id
      }).then(extractHits).then(mapHits);
    },

    get: function get(id, version) {
      var versionProm = Promise.resolve(version);
      if (!version) {
        versionProm = api.getLatestVersion(id);
      }
      return versionProm.then(function (v) {
        return client.get({
          index: nodesIndex,
          type: id,
          id: id + '@' + _semver2.default.clean(v)
        });
      }).then(getSource);
    },

    versions: function versions(id) {
      return api.list(id).then(_lodash2.default.partial(_lodash2.default.map, _lodash2.default, function (s) {
        return s.version;
      }));
    },

    setMeta: function setMeta(node, version, key, meta) {
      return client.update({
        index: metaIndex,
        type: node,
        id: node + '@' + _semver2.default.clean(version),
        body: {
          script: 'ctx._source.elements += element',
          params: {
            element: {
              version: _semver2.default.clean(version),
              key: key,
              data: JSON.stringify(meta)
            }
          }
        }
      });
    },

    getMeta: function getMeta(node, version, key) {
      return client.get({
        index: metaIndex,
        type: node,
        id: node + '@' + _semver2.default.clean(version)
      }).then(getSource).then(function (meta) {
        var elem = (0, _lodash2.default)(meta.elements).chain().filter(function (m) {
          return m.key === key;
        }).findLast(function (m) {
          return _semver2.default.satisfies(m.version, version);
        }).value();
        return elem ? JSON.parse(elem.data) : undefined;
      });
    },

    getLatestMeta: function getLatestMeta(node, key) {
      return api.getLatestVersion(node).then(function (version) {
        return api.getMeta(node, version, key);
      });
    },

    getAllMeta: function getAllMeta(node, version) {
      return client.get({
        index: metaIndex,
        type: node,
        id: node + '@' + _semver2.default.clean(version)
      }).then(getSource).then(function (meta) {
        var elem = (0, _lodash2.default)(meta.elements).chain().filter(function (m) {
          return _semver2.default.satisfies(m.version, version);
        }).map(function (m) {
          return { key: m.key, data: m.data };
        }).keyBy('key').value();
        return elem;
      });
    },

    setCode: function setCode(node, validity, language, meta) {
      return api.setMeta(node, validity, 'code/' + language, meta);
    },

    getCode: function getCode(node, version, language) {
      return api.getMeta(node, version, 'code/' + language);
    },

    getLatestCode: function getLatestCode(node, language) {
      return api.getLatestMeta(node, 'code/' + language);
    },

    setConfig: function setConfig(type, config, value) {
      return new Promise(function (resolve) {
        return client.index({
          index: configIndex,
          type: type,
          id: config,
          body: {
            value: value
          }
        }).then(function (config) {
          return resolve(config);
        }).catch(function () {
          return resolve();
        });
      });
    },

    getConfig: function getConfig(type, config) {
      return client.get({
        index: configIndex,
        type: type,
        id: config
      }).then(getSource).then(function (v) {
        return v.value;
      });
    },

    flush: function flush() {
      return Promise.all(_lodash2.default.map(indices, function (i) {
        return client.indices.refresh({ index: i });
      }));
    },

    statistics: function statistics() {
      return Promise.all([client.count({ index: nodesIndex })]).then(function (count) {
        return { nodeCount: count };
      });
    },

    getLatestVersion: function getLatestVersion(node) {
      return api.list(node).then(function (list) {
        return _lodash2.default.last(list.sort(function (a, b) {
          return _semver2.default.compare(a.version, b.version);
        })).version;
      });
    },

    insert: function insert(node) {
      var copyMetadata = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1];

      var isValid = valid(node);
      if (isValid.valid === true) {
        var normNode = normalizeNode(node);
        return client.create({
          index: nodesIndex,
          type: normNode.id,
          id: normNode.id + '@' + normNode.version,
          body: normNode
        }).then(function () {
          return api.predecessorMeta(normNode.id, normNode.version);
        }).then(function (predMeta) {
          var elements = (typeof predMeta === 'undefined' ? 'undefined' : _typeof(predMeta)) === 'object' && Array.isArray(predMeta._source.elements) ? predMeta._source.elements : [];
          elements = _lodash2.default.map(elements, function (e) {
            return _lodash2.default.merge({}, e, { version: normNode.version });
          });
          if (!copyMetadata) {
            elements = [];
          }
          return client.create({
            index: metaIndex,
            type: normNode.id,
            id: normNode.id + '@' + normNode.version,
            body: {
              elements: elements
            }
          });
        });
      } else {
        return Promise.reject(isValid.error);
      }
    },

    // returns the node that precedes
    predecessor: function predecessor(nodeId, version) {
      return api.list(nodeId).then(function (list) {
        var older = _lodash2.default.filter(list, function (i) {
          return _semver2.default.lt(i.version, version);
        });
        return _lodash2.default.last(older.sort(function (a, b) {
          return _semver2.default.compare(a.version, b.version);
        }));
      });
    },

    predecessorMeta: function predecessorMeta(nodeId, version) {
      return new Promise(function (resolve, reject) {
        api.predecessor(nodeId, version).then(function (pred) {
          return client.get({
            index: metaIndex,
            type: pred.id,
            id: pred.id + '@' + pred.version
          });
        }).then(function (predMeta) {
          resolve(predMeta);
        }).catch(function () {
          // if there is no predecessor resolve with nothing
          resolve();
        });
      });
    },

    init: function init() {
      return Promise.all(_lodash2.default.map(indices, function (i) {
        return client.indices.exists({ index: i });
      })).then(function (ex) {
        var createIndices = (0, _lodash2.default)(ex).chain().zip(indices).reject(function (zipped) {
          return zipped[0];
        }).map(function (zipped) {
          return client.indices.create({ index: zipped[1] });
        }).value();
        return Promise.all(createIndices);
      });
    },

    clear: function clear() {
      if (prefix === '') {
        throw new Error('Will not clear unprefixed Database');
      } else {
        return Promise.all(_lodash2.default.map(indices, function (i) {
          return clearIndex(api, client, i);
        }));
      }
    }
  };

  return api;
}