'use strict';

var deepEqual = require('deep-equal')
var pull = require('pull-stream')
var contpara = require('continuable-para')
// make a validation stream?
// read the latest record in the database
// check it against the incoming data,
// and then read through

function clone (obj) {
  var o = {}
  for(var k in obj) o[k] = obj[k];
  return o
}

function get (db, key) {
  return function (cb) {
    return db.get(key, cb)
  }
}

function isString (s) {
  return 'string' === typeof s
}

module.exports = function (ssb, opts) {

  var lastDB = ssb.sublevel('lst')
  var hash = opts.hash
  var zeros = opts.hash(new Buffer(0))
      zeros.fill(0)

  var verify = opts.keys.verify
  var encode = opts.codec.encode

  var validators = {}

  function validateSync (msg, prev, pub) {
    var type = msg.content.type
    if(!isString(type)) {
      validateSync.reason = 'type property must be string'
      return false
    }

    if(32 < type.length || type.length < 3) {
      validateSync.reason = 'type must be 3 < length <= 32, but was:' + type.length
      return false
    }

    if(prev) {
      if(!deepEqual(msg.previous, hash(encode(prev)))) {

        validateSync.reason = 'expected previous: '
          + hash(encode(prev)).toString('base64')

        return false
      }
      if(msg.sequence === prev.sequence + 1 
        && msg.timestamp < prev.timestamp) {

          validateSync.reason = 'out of order'

          return false
      }
    }
    else {
      if(!deepEqual(msg.previous, zeros)
        && msg.sequence === 1 && msg.timestamp > 0) {

          validateSync.reason = 'expected initial message'

          return false
      }
    }
    if(!deepEqual(msg.author, hash(pub.public || pub))) {

      validateSync.reason = 'expected different author:'+
        hash(pub.public || pub).toString('base64') +
        'but found:' +
        msg.author.toString('base64')

      return false
    }

    var _msg = clone(msg)
    delete _msg.signature
    if(!verify(pub, msg.signature, hash(encode(_msg)))) {

      validateSync.reason = 'signature was invalid'

      return false
    }
    return true
  }

  function createValidator (id) {

    var queue = [], batch = [], prev, pub, cbs = []

    return function (msg, cb) {

      // INIT: retrive the latest message for the given id.
      // when not in INIT state, queue any new messages.
      // DRAIN: validate all queued messages.
      // writes all valid message.
      // after writing, if there are more queued queued messages
      // goto DRAIN

      var msghash = hash(encode(msg))

      if(!queue.length && !batch.length) {

        queue.push({hash: msghash, msg: msg, cb: cb})

        contpara(
          get(ssb, msg.previous),
          ssb.getPublicKey(msg.author),
          get(lastDB, msg.author)
        )(function (err, results) {
          prev = err ? null : results[0]
          //get PUBLIC KEY out of FIRST MESSAGE.
          pub = err ? msg.content.public : results[1]

          var expected = err ? 1 : results[2] + 1
          if(expected != msg.sequence) {
            queue.shift()
            return cb(
              new Error('sequence out of order, expected:'
                + expected + ' got: ' + msg.sequence)
            )
          }

          drain()
        })

      }
      else
        queue.push({hash: msghash, msg: msg, cb: cb})
    }

    function drain () {
      while(queue.length) {
        var e = queue.shift()
        if(validateSync(e.msg, prev, pub)) {
          cbs.push(e)
          batch.push({
            key: e.hash, value: e.msg, type: 'put'
          })
          prev = e.msg
        }
        else
          e.cb(new Error(validateSync.reason))
      }

      ssb.batch(batch, function (err) {
        while(cbs.length) {
          var e = cbs.shift()
          e.cb(err, e.msg, e.hash)
        }
        batch = [];
        if(queue.length) drain()
        else delete validators[id.toString('base64')]
      })
    }
  }

  var v
  return v = {
    validateSync: validateSync,
    validate: function (msg, cb) {

      var id = msg.author
      var ids = id.toString('base64')
      var validator = validators[ids] =
        validators[ids] || createValidator(id)

      return validator(msg, cb)
    }
  }
}

@@ -599,16 +599,34 @@
 zeros = 
+undefined%0A      //
 opts.has
@@ -648,16 +648,18 @@
 )%0A      
+//
 zeros.fi
@@ -1494,33 +1494,24 @@
 %7B%0A      if(!
-deepEqual
 (msg.previou
@@ -1515,16 +1515,16 @@
 ious
-, zeros)
+ == null
 %0A   
@@ -1571,16 +1571,17 @@
 amp %3E 0)
+)
  %7B%0A%0A    
@@ -2116,24 +2116,53 @@
 false%0A    %7D%0A
+    validateSync.reason = ''%0A
     return t

allow previous on init to be nullish