'use strict'

var level = require('level-browserify')
var sublevel = require('level-sublevel')
var msgpack = require('msgpack5')

function Store (options) {
  if (!(this instanceof Store)) {
    return new Store(options)
  }

  if (!options.level) {
    throw new Error('missing level')
  }

  this._level = options.level
  this._levelOpts = {
    valueEncoding: msgpack()
  }
  this._sameDateCount = 0
  this._prevDate = null
}

const zeroPadding = (function () {
  // width and padding calculates only once
  const width = parseInt(Number.MAX_SAFE_INTEGER).toString(10).length
  const padding = new Array(width + 1).join('0')
  return function (num) {
    return (padding + num).slice(-width)
  }
})()

Store.prototype.put = function (packet, cb) {
  var date = new Date().toISOString()
  if (this._prevDate === date) {
    ++this._sameDateCount
  } else {
    this._sameDateCount = 0
    this._prevDate = date
  }
  date += zeroPadding(this._sameDateCount)

  var that = this
  this._level.get(
    'packets~' + packet.messageId,
    this._levelOpts,
    function (err, _date) {
      if (err) {
        if (!err.notFound) return cb(err)
        var cmd = [
          {type: 'put', key: 'packets~' + packet.messageId, value: date},
          {type: 'put', key: 'packet-by-date~' + date + '~' + packet.messageId, value: packet}
        ]
        that._level.batch(cmd, that._levelOpts, cb)
      } else {
        that._level.get(
          'packet-by-date~' + _date + '~' + packet.messageId,
          that._levelOpts,
          function (err, _packet) {
            if (err) return cb(err)
            if (packet.cmd === _packet.cmd) return cb()
            that._level.put('packet-by-date~' + _date + '~' + packet.messageId, packet, that._levelOpts, cb)
          })
      }
    })
  return this
}

Store.prototype.get = function (packet, cb) {
  var that = this
  this._level.get(
    'packets~' + packet.messageId,
    this._levelOpts,
    function (err, date) {
      if (err) return cb(err)
      that._level.get(
        'packet-by-date~' + date + '~' + packet.messageId,
        that._levelOpts,
        cb)
    })
  return this
}

Store.prototype.del = function (packet, cb) {
  var that = this
  this._level.get(
    'packets~' + packet.messageId,
    this._levelOpts,
    function (err, date) {
      if (err) return cb(err)
      that._level.get(
        'packet-by-date~' + date + '~' + packet.messageId,
        that._levelOpts,
        function (err, _packet) {
          if (err) return cb(err)
          var cmd = [
            {type: 'del', key: 'packets~' + packet.messageId},
            {type: 'del', key: 'packet-by-date~' + date + '~' + packet.messageId}
          ]
          that._level.batch(
            cmd,
            that._levelOpts,
            function (err) {
              if (err) return cb(err)
              cb(null, _packet)
            })
        })
    })
  return this
}

Store.prototype.createStream = function () {
  var opts = this._levelOpts
  opts.keys = false
  opts.lt = 'packet-by-date~\xff'
  opts.gt = 'packet-by-date~'
  return this._level.createReadStream(opts)
}

Store.prototype.close = function (cb) {
  this._level.close(cb)
  return this
}

function Manager (path, options) {
  if (!(this instanceof Manager)) {
    return new Manager(path, options)
  }

  if (typeof path === 'object') {
    options = path
    path = null
  }

  if (options && options.level) {
    this._level = options.level
  } else {
    this._level = level(path, options)
  }

  this._sublevel = sublevel(this._level)
  this.incoming = new Store({ level: this._sublevel.sublevel('incoming') })
  this.outgoing = new Store({ level: this._sublevel.sublevel('outgoing') })
}

Manager.single = Store

Manager.prototype.close = function (done) {
  this.incoming.close()
  this.outgoing.close()
  this._sublevel.close()
  this._level.close(done)
}

module.exports = Manager

@@ -3764,24 +3764,42 @@
 on (done) %7B%0A
+  var that = this%0A
   this.incom
@@ -3812,83 +3812,188 @@
 ose(
-)%0A  this.outgoing.close()%0A  this._sublevel.close()%0A  this._level.close(done
+function () %7B%0A    that.outgoing.close(function () %7B%0A      that._sublevel.close(function () %7B%0A        that._level.close(function () %7B%0A          done()%0A        %7D)%0A      %7D)%0A    %7D)%0A  %7D
 )%0A%7D%0A

Use callback style in `Manager.close()`.