/**
 * Simple synchronous parser based on node-binary.
 */

var imports     = require('soop').imports();
function Parser(buffer)
{
  this.subject = buffer;
  this.pos = 0;
};

Parser.prototype.buffer = function buffer(len) {
  var buf = this.subject.slice(this.pos, this.pos+len);
  this.pos += len;
  return buf;
};

Parser.prototype.search = function search(needle) {
  var len;

  if ("string" === typeof needle || Buffer.isBuffer(needle)) {
    // TODO: Slicing is probably too slow
    len = this.subject.slice(this.pos).indexOf(needle);
    if (len !== -1) {
      this.pos += len + needle.length;
    }
    return len;
  }
  if ("number" === typeof needle) {
    needle = needle & 0xff;
    // Search for single byte
    for (var i = this.pos, l = this.subject.length; i < l; i++) {
      if (this.subject[i] == needle) {
        len = i - this.pos;
        this.pos = i+1;
        return len;
      }
    }
    return -1;
  }
};

/**
  * Like search(), but returns the skipped bytes
  */
Parser.prototype.scan = function scan(needle) {
  var startPos = this.pos;
  var len = this.search(needle);
  if (len !== -1) {
    return this.subject.slice(startPos, startPos+len);
  } else {
    throw new Error('No match');
  }
};

Parser.prototype.eof = function eof() {
  return this.pos >= this.subject.length;
};

// convert byte strings to unsigned little endian numbers
function decodeLEu (bytes) {
  var acc = 0;
  for (var i = 0; i < bytes.length; i++) {
    acc += Math.pow(256,i) * bytes[i];
  }
  return acc;
}

// convert byte strings to unsigned big endian numbers
function decodeBEu (bytes) {
  var acc = 0;
  for (var i = 0; i < bytes.length; i++) {
    acc += Math.pow(256, bytes.length - i - 1) * bytes[i];
  }
  return acc;
}

// convert byte strings to signed big endian numbers
function decodeBEs (bytes) {
  var val = decodeBEu(bytes);
  if ((bytes[0] & 0x80) == 0x80) {
    val -= Math.pow(256, bytes.length);
  }
  return val;
}

// convert byte strings to signed little endian numbers
function decodeLEs (bytes) {
  var val = decodeLEu(bytes);
  if ((bytes[bytes.length - 1] & 0x80) == 0x80) {
    val -= Math.pow(256, bytes.length);
  }
  return val;
}

function getDecoder(len, fn) {
  return function () {
    var buf = this.buffer(len);
    return fn(buf);
  };
};
[ 1, 2, 4, 8 ].forEach(function (bytes) {
  var bits = bytes * 8;
  
  Parser.prototype['word' + bits + 'le']
    = Parser.prototype['word' + bits + 'lu']
    = getDecoder(bytes, decodeLEu);
  
  Parser.prototype['word' + bits + 'ls']
    = getDecoder(bytes, decodeLEs);
  
  Parser.prototype['word' + bits + 'be']
    = Parser.prototype['word' + bits + 'bu']
    = getDecoder(bytes, decodeBEu);
  
  Parser.prototype['word' + bits + 'bs']
    = getDecoder(bytes, decodeBEs);

  Parser.prototype.word8 = Parser.prototype.word8u = Parser.prototype.word8be;
  Parser.prototype.word8s = Parser.prototype.word8bs;
});

Parser.prototype.varInt = function ()
{
  var firstByte = this.word8();
  switch (firstByte) {
  case 0xFD:
    return this.word16le();

  case 0xFE:
    return this.word32le();

  case 0xFF:
    return this.word64le();

  default:
    return firstByte;
  }
};

Parser.prototype.varStr = function () {
  var len = this.varInt();
  return this.buffer(len);
};

module.exports = require('soop')(Parser);