var assert = require('./util').assert
var base = require('./base')
var codecs = require('./codecs')

var bytewise = exports

//
// expose type information
//
var sorts = bytewise.sorts = base.sorts
bytewise.bound = base.bound
bytewise.compare = base.compare
bytewise.equal = base.equal

//
// generate a buffer with type's byte prefix from source value
//
function serialize(type, source, options) {
  var codec = type.codec
  if (!codec)
    return postEncode(new Buffer([ type.byte ]), options)

  var buffer = codec.encode(source, bytewise)

  if (options && options.nested && codec.escape)
    buffer = codec.escape(buffer)

  var hint = typeof codec.length === 'number' ? (codec.length + 1) : void 0 
  var buffers = [ new Buffer([ type.byte ]), buffer ]
  return postEncode(Buffer.concat(buffers, hint), options)
}

//
// core encode logic
//
bytewise.encode = function(source, options) {

  // check for invalid/incomparable values
  assert(!base.invalid(source), 'Invalid value')

  // encode bound types (ranges)
  var boundary = base.bound.getBoundary(source)
  if (boundary)
    return boundary.encode(source, bytewise)

  // encode standard value-typed sorts
  var order = base.order
  var sort
  for (var i = 0, length = order.length; i < length; ++i) {
    sort = sorts[order[i]]

    if (sort.is(source)) {

      // loop over any subsorts defined on sort
      // TODO: clean up
      var subsorts = sort.sorts ||  { '': sort }
      for (key in subsorts) {
        var subsort = subsorts[key]
        if (subsort.is(source)) 
          return serialize(subsort, source, options)
      }

      // source is an unsupported subsort
      assert(false, 'Unsupported sort value')
    }
  }

  // no type descriptor found
  assert(false, 'Unknown value')
}

//
// core decode logic
//
bytewise.decode = function (buffer, options) {
  // attempt to decode string input using configurable codec
  if (typeof buffer === 'string') {
    buffer = bytewise.stringCodec.encode(buffer)
  }

  assert(!buffer || !buffer.undecodable, 'Encoded value not decodable')

  var byte = buffer[0]
  var type = bytewise.getType(byte)
  assert(type, 'Invalid encoding: ' + buffer)

  // if type provides a decoder it is passed the base type system as second arg
  var codec = type.codec
  if (codec) {
    var decoded = codec.decode(buffer.slice(1), bytewise)

    if (options && options.nested && codec.unescape)
      decoded = codec.unescape(decoded)

    return postDecode(decoded, options)
  }

  // nullary types without a codec must provide a value for their decoded form
  assert('value' in type, 'Unsupported encoding: ' + buffer)
  return postDecode(type.value, options)
}

//
// process top level
//
function postEncode(encoded, options) {
  if (options === null)
    return encoded

  return bytewise.postEncode(encoded, options)
}

//
// invoked after encoding with encoded buffer instance
//
bytewise.postEncode = function (encoded, options) {

  // override buffer toString method to default to hex to help coercion issues
  // TODO: just return pure buffer, do this toString hackery in bytewise
  encoded.toString = function (encoding) {
    if (!encoding)
      return bytewise.stringCodec.decode(encoded)

    return Buffer.prototype.toString.apply(encoded, arguments)
  }

  return encoded
}

function postDecode(decoded, options) {
  if (options === null)
    return decoded

  return bytewise.postDecode(decoded, options)
}

//
// invoked after decoding with decoded value
//
bytewise.postDecode = function (decoded, options) {
  return decoded
}


//
// registry mapping byte prefixes to type descriptors
//
var PREFIX_REGISTRY

function registerType(type) {
  var byte = type && type.byte
  if (byte == null)
    return

  if (byte in PREFIX_REGISTRY)
    assert.deepEqual(type, PREFIX_REGISTRY[byte], 'Duplicate prefix: ' + byte)

  PREFIX_REGISTRY[type.byte] = type
}

function registerTypes(types) {
  for (var key in types) {
    registerType(types[key])
  }
}

//
// look up type descriptor associated with a given byte prefix
//
bytewise.getType = function (byte) {

  // construct and memoize byte prefix registry on first run
  if (!PREFIX_REGISTRY) {
    PREFIX_REGISTRY = {}

    // register sorts
    var sort
    for (var key in sorts) {
      sort = sorts[key]

      // if sort has subsorts register these instead
      sort.sorts ? registerTypes(sort.sorts) : registerType(sort)
    }
  }

  return PREFIX_REGISTRY[byte]
}

bytewise.buffer = true
bytewise.stringCodec = codecs.HEX
bytewise.type = 'bytewise-core'

