var crypto = require('crypto');
var bignum = require('bignum');

var globalBuffer = new Buffer(1024);
var zerobuf = new Buffer(0);
var ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
var ALPHABET_ZERO = ALPHABET[0];
var ALPHABET_BUF = new Buffer(ALPHABET, 'ascii');
var ALPHABET_INV = {};
for (var i = 0; i < ALPHABET.length; i++) {
  ALPHABET_INV[ALPHABET[i]] = i;
};

// Vanilla Base58 Encoding
var base58 = {
  encode: function(buf) {
    var str;
    var x = bignum.fromBuffer(buf);
    var r;

    if (buf.length < 512) {
      str = globalBuffer;
    } else {
      str = new Buffer(buf.length << 1);
    }
    var i = str.length - 1;
    while (x.gt(0)) {
      r = x.mod(58);
      x = x.div(58);
      str[i] = ALPHABET_BUF[r.toNumber()];
      i--;
    }

    // deal with leading zeros
    var j = 0;
    while (buf[j] == 0) {
      str[i] = ALPHABET_BUF[0];
      j++;
      i--;
    }

    return str.slice(i + 1, str.length).toString('ascii');
  },

  decode: function(str) {
    if (str.length == 0) return zerobuf;
    var answer = bignum(0);
    for (var i = 0; i < str.length; i++) {
      answer = answer.mul(58);
      answer = answer.add(ALPHABET_INV[str[i]]);
    };
    var i = 0;
    while (i < str.length && str[i] == ALPHABET_ZERO) {
      i++;
    }
    if (i > 0) {
      var zb = new Buffer(i);
      zb.fill(0);
      if (i == str.length) return zb;
      answer = answer.toBuffer();
      return Buffer.concat([zb, answer], i + answer.length);
    } else {
      return answer.toBuffer();
    }
  },
};

// Base58Check Encoding
function sha256(data) {
  return new Buffer(crypto.createHash('sha256').update(data).digest('binary'), 'binary');
};

function doubleSHA256(data) {
  return sha256(sha256(data));
};

var base58Check = {
  encode: function(buf) {
    var checkedBuf = new Buffer(buf.length + 4);
    var hash = doubleSHA256(buf);
    buf.copy(checkedBuf);
    hash.copy(checkedBuf, buf.length);
    return base58.encode(checkedBuf);
  },

  decode: function(s) {
    var buf = base58.decode(s);
    if (buf.length < 4) {
      throw new Error("invalid input: too short");
    }

    var data = buf.slice(0, -4);
    var csum = buf.slice(-4);

    var hash = doubleSHA256(data);
    var hash4 = hash.slice(0, 4);

    if (csum.toString('hex') !== hash4.toString('hex')) {
      throw new Error("checksum mismatch");
    }

    return data;
  },
};

// if you frequently do base58 encodings with data larger
// than 512 bytes, you can use this method to expand the
// size of the reusable buffer
exports.setBuffer = function(buf) {
  globalBuffer = buf;
};

exports.base58 = base58;
exports.base58Check = base58Check;
exports.encode = base58.encode;
exports.decode = base58.decode;
