const { clone } = require('lodash');
const Levelup = require('levelup');
const uuid = require('node-uuid');
const serialize = require('../metadata/serialize');
const Multihashing = require('multihashing');
const Multihash = require('multihashes');

const DefaultOptions = {
  backend: 'memory'
};

class Datastore {

  constructor(options = null) {
    if (options == null) {
      options = DefaultOptions;
    } else {
      options = Object.assign(clone(DefaultOptions), options);
    }

    const levelOpts = {};
    switch (options.backend) {
      case 'memory':
        levelOpts.db = require('memdown');
        break;

      default:
        throw new Error(`Datastore backend ${ options.backend } not supported`);
    }

    levelOpts.valueEncoding = valueCodec;
    const location = options.location || '/aleph/data-' + uuid.v4();

    this.db = Levelup(location, levelOpts);
    this.location = location;
  }

  put(value) {
    if (!Buffer.isBuffer(value)) {
      value = serialize.encode(value);
    }

    const mh = Multihashing(value, 'sha2-256');
    const key = Multihash.toB58String(mh);

    return new Promise((resolve, reject) => {
      this.db.put(key, value, { sync: true }, err => {
        if (err) return reject(err);
        resolve(key);
      });
    });
  }

  get(key, opts = {}) {
    return new Promise((resolve, reject) => {
      this.db.get(key, (err, val) => {
        if (err) return reject(err);

        if (opts.returnRawBuffer === true) {
          return resolve(val);
        }

        try {
          return resolve(serialize.decode(val));
        } catch (err) {
          return resolve(val.toString('base64'));
        }
      });
    });
  }

  has(key) {
    return this.get(key, { returnRawBuffer: true }).then(() => true).catch(err => {
      if (err.notFound) return false;
      throw err;
    });
  }
}

const valueCodec = {
  type: 'noop',
  buffer: true,

  encode(val) {
    return val;
  },

  decode(buf) {
    return buf;
  }
};

module.exports = {
  Datastore
};