var Buffer = require("buffer").Buffer;

var crypto = require("crypto");

var BP = {};


BP.bitSize = function() {
    return (this.length * 8);
}

BP.add = function(buf) {
    var n1 = bigi.str2bigInt(this.toString("hex"), 16, (this.length * 8));
    var n2 = bigi.str2bigInt(buf.toString("hex"), 16, (buf.length * 8));
    var res = bigi.add(n1, n2);
    var tt = bigi.bigInt2str(res, 16);
    var n = nb(tt.length % 2 === 0 ? tt : "0" + tt, 'hex');
    return n
}
BP.sub = function(buf) {
    var n1 = bigi.str2bigInt(this.toString("hex"), 16, (this.length * 8));
    var n2 = bigi.str2bigInt(buf.toString("hex"), 16, (buf.length * 8));
    var res = bigi.sub(n1, n2);
    var tt = bigi.bigInt2str(res, 16);
    var n = nb(tt.length % 2 === 0 ? tt : "0" + tt, 'hex');
    return n
}
var bii = require("big-integer");
BP.nextPrime = function(buf) {
    var as = buf.add(nb("1",10));
    var aa = as.toBase(10);
    var res =  bii(aa).isPrime()

    var ff= ["0","2","4","5","6","8"];
    while(!res){
        as = as.add(nb("1",10));
        aa = as.toBase(10);
        var las = aa.substring(aa.length-1);
        if(ff.indexOf(las+"") === -1 ){
         res = bii(aa).isPrime()
        }
      }  

    return as
}



BP.mult = function(buf) {
    var string = this.toString('hex');
    var asa = bigi.str2bigInt(string, 16, (this.length * 8));
    var asa2;
    if (typeof(buf) === "number") {
        asa2 = bigi.str2bigInt("" + buf, 10);
    } else {
        var string2 = buf.toString('hex');
        asa2 = bigi.str2bigInt(string2, 16, (buf.length * 8));
    }
    bigi.mult(asa, asa2);
    var ss = bigi.bigInt2str(asa, 16);
    var re = nb(ss.length % 2 === 0 ? ss : "0" + ss, 'hex');
    return re;
}

BP.div = function(buf) {
    var n1 = bigi.str2bigInt(this.toString("hex"), 16, (this.length * 8));
    var n2 = bigi.str2bigInt(buf.toString("hex"), 16, (buf.length * 8));
    var q = bigi.str2bigInt("0", 16, n1.length * 8);
    var r = bigi.str2bigInt("0", 16, n2.length * 8);
    bigi.div(n1, n2, q, r);
    var qq = bigi.bigInt2str(q, 16);
    var rr = bigi.bigInt2str(r, 16);
    var ret = [nb(qq.length % 2 === 0 ? qq : "0" + qq, 'hex'), nb(rr.length % 2 === 0 ? rr : "0" + rr, 'hex')];
    return ret;
}

BP.gcd = function(buf) {
    var n1 = bigi.str2bigInt(this.toString("hex"), 16, (this.length * 8));
    var n2 = bigi.str2bigInt(buf.toString("hex"), 16, (buf.length * 8));
    var gg = bigi.GCD(n1, n2);
    var qq = bigi.bigInt2str(gg, 16);
    return nb(qq.length % 2 === 0 ? qq : "0" + qq, 'hex');
}
BP.mod = function(buf) {
    var n1 = bigi.str2bigInt(this.toString("hex"), 16, (this.length * 8));
    var n2 = bigi.str2bigInt(buf.toString("hex"), 16, (buf.length * 8));
    var gg = bigi.mod(n1, n2);
    var qq = bigi.bigInt2str(gg, 16);
    return nb(qq.length % 2 === 0 ? qq : "0" + qq, 'hex');
}
BP.powMod = function(buf, bufn) {
    var n1 = bigi.str2bigInt(this.toString("hex"), 16, (this.length * 8));
    var n2 = bigi.str2bigInt(buf.toString("hex"), 16, (buf.length * 8));
    var n3 = bigi.str2bigInt(bufn.toString("hex"), 16, (bufn.length * 8));
    var gg = bigi.powMod(n1, n2, n3);
    var qq = bigi.bigInt2str(gg, 16);
    return nb(qq.length % 2 === 0 ? qq : "0" + qq, 'hex');
}


BP.leftShift = function(n) {
    var n1 = bigi.str2bigInt(this.toString("hex"), 16, (this.length * 8));
    var gg = bigi.leftShift(n1, n);
    var qq = bigi.bigInt2str(n1, 16);
    return nb(qq.length % 2 === 0 ? qq : "0" + qq, 'hex');
}
BP.rightShift = function(n) {
    var n1 = bigi.str2bigInt(this.toString("hex"), 16, (this.length * 8));
    var gg = bigi.rightShift(n1, n);
    var qq = bigi.bigInt2str(n1, 16);
    return nb(qq.length % 2 === 0 ? qq : "0" + qq, 'hex');
}




BP.SHA256 = function(encoding) {
    var encoding = encoding || null;
    var h = crypto.createHash('sha256');
    h.update(new Buffer(this));
    return nb(h.digest(encoding));
}
BP.SHA1 = function(encoding) {
    var encoding = encoding || null;
    var h = crypto.createHash('sha1');
    h.update(this);
    return nb(h.digest(encoding));
}
BP.SHA224 = function(encoding) {
    var encoding = encoding || null;
    var h = crypto.createHash('sha224');
    h.update(this);
    return nb(h.digest(encoding));
}
BP.SHA256 = function(encoding) {
    var encoding = encoding || null;
    var h = crypto.createHash('sha256');
    h.update(this);
    return nb(h.digest(encoding));
}
BP.SHA384 = function(encoding) {
    var encoding = encoding || null;
    var h = crypto.createHash('sha384');
    h.update(this);
    return nb(h.digest(encoding));
}
BP.SHA512 = function(encoding) {
    var encoding = encoding || null;
    var h = crypto.createHash('sha512');
    h.update(this);
    return nb(h.digest(encoding));
}

BP.MD5 = function(encoding) {
    var encoding = encoding || null;
    var h = crypto.createHash('md5');
    h.update(this);
    return h.digest(encoding);
}
BP.RIPE = function(encoding) {
    var encoding = encoding || null;
    var h = crypto.createHash('ripemd');
    h.update(this);
    return nb(h.digest(encoding));
}
BP.RIPE160 = function(encoding) {
    var encoding = encoding || null;
    var h = crypto.createHash('ripemd160');
    h.update(this);
    return nb(h.digest(encoding));
}

BP.WHIRLPOOL = function(encoding) {
    var encoding = encoding || null;
    var h = crypto.createHash('whirlpool');
    h.update(this);
    return nb(h.digest(encoding));
}

BP.prepend = function(buf) {
    var tmp = nb(this.length + buf.length);
    for (var i = 0; i < buf.length; i++) {
        tmp[i] = buf[i];
    }
    for (var i = 0; i < this.length; i++) {
        tmp[buf.length + i] = this[i];
    }
    return tmp;
}

BP.append = function(buf, baselength) {
    var nlen = this.length + buf.length;
    if (baselength) {
        nlen = Math.ceil((nlen) / baselength) * baselength;
    }
    var tmp = nb(nlen);
    tmp.fill(0);
    for (var i = 0; i < this.length; i++) {
        tmp[i] = this[i];
    }
    for (var i = 0; i < buf.length; i++) {
        tmp[this.length + i] = buf[i];
    }
    return tmp;
}


BP.XOR = function(buf) {
    var nn = nb(this.length);
    var mi = buf.length;
    var fi = 0;
    var n = this.length;
    for (var i = 0; i < n; i++) {
        if (i > mi) {
            fi = 0;
        }
        nn[i] = this[i] ^ (buf[fi]);
        fi++;
    }
    return nn;
}
BP.AND = function(buf) {
    var nn = nb(this.length);
    var mi = buf.length;
    var fi = 0;
    var n = this.length;
    for (var i = 0; i < n; i++) {
        if (i > mi) {
            fi = 0;
        }
        nn[i] = this[i] & (buf[fi]);
        fi++;
    }
    return nn;
}
BP.OR = function(buf) {
    var nn = nb(this.length);
    var mi = buf.length;
    var fi = 0;
    var n = this.length;
    for (var i = 0; i < n; i++) {
        if (i > mi) {
            fi = 0;
        }
        nn[i] = this[i] | (buf[fi]);
        fi++;
    }
    return nn;
}

BP.clone = function() {
    var n = nb(this.length);
    for (var i = 0; i < this.length; i++) {
        n[i] = this[i];
    }
    return n;
}


var bigi = require("./bigint");

BP.toBase = function(base) {
    var ss = this.toString("hex");
    var asa = bigi.str2bigInt(ss, 16);
    var s = bigi.bigInt2str(asa, base);
    return s;
}




BP.fromBase = function(string, base) {
    var asa = bigi.str2bigInt(string, base);
    var ss = bigi.bigInt2str(asa, 16);

    var re = nb(ss.length % 2 === 0 ? ss : "0" + ss, 'hex');
    return re;
}
BP.fromBase2 = function(base) {
    var string = this.toString();
    var asa = bigi.str2bigInt(string, base);
    var ss = bigi.bigInt2str(asa, 16);
    var re = nb(ss.length % 2 === 0 ? ss : "0" + ss, 'hex');
    return re;
}


var zlib = require('zlib');

BP.zip = function(callback) {

    zlib.deflate(this, function(err, buffer) {
        callback(err, nb(buffer));
    });
}

BP.unzip = function(callback) {

    zlib.unzip(this, function(err, buffer) {
        callback(err, nb(buffer));
    });
}




BP.hexdump = function() {
    return hexdump.apply(this, arguments);
}

function hexdump(buf, showascii, perline, space, padit, linenums, showtype, html) {
    var showtype = showtype || false;
    var html = html || false;
    var s = "";
    if (showtype) {
        s += "type: " + typeof(buf) + " " + ((buf instanceof Buffer)) + "\n";
    }
    if (typeof(buf) !== "object") {
        buf = new Buffer(buf);
    }

    var usebuf;
    var perline = (perline === 0 || perline) ? perline : 32;
    var space = (space === 0 || space) ? space : 8;
    var showascii = showascii || false;
    var linenums = linenums || false;
    if (perline === 0) {
        perline = buf.length;
    }
    usebuf = buf;
    if (padit) {
        var shouldbelength = Math.ceil(buf.length / perline) * perline;
        var nbuf = new Buffer(shouldbelength);
        nbuf.fill(0);
        buf.copy(nbuf, 0, 0, buf.length);
        usebuf = nbuf;
    }

    var tl = Math.ceil(buf.length / perline);

    var mask = [127, 129, 141, 143, 144, 157, 160, 173, 184, 185, 186];

    var allow = [];


    for (var i = 0; i < tl; i++) {
        var mx = (i * perline) + perline;
        if (mx > usebuf.length) {
            mx = usebuf.length;
        }
        if (linenums) {
            s += intToHex(i * perline, 3) + " ";

        }
        var a = "";
        var t = usebuf.slice(i * perline, mx);
        for (var y = 0; y < t.length; y++) {
            s += int2hex(t[y]);
            if (html) {
                if (((t[y] > 31) && (mask.indexOf(t[y]) === -1))) {
                    a += "&#" + t[y] + ";";
                } else {
                    a += ".";
                }
            } else {
                if (((t[y] > 31) && (t[y] < 127))) {
                    a += String.fromCharCode(t[y]);
                } else {
                    a += ".";
                }
            }


            if (y % space === (space - 1)) {
                s += " ";
                a += " ";
            }

        }

        if (showascii) {
            s += " | " + a;
        }
        if (tl > 1) {
            s += "\n";

        }

    }

    return s;

}

function intToHex(integera, bytes) {
    var bytes = bytes || 1;
    integera = integera.toString(16);
    if (integera.length % (bytes * 2) !== 0) {
        while (integera.length < bytes * 2) {
            integera = '0' + integera;
        }
    }
    return integera;
};

function int2hex(integer) {
    integer = integer.toString(16);
    if (integer.length % 2 !== 0) {
        integer = '0' + integer;
    }
    return integer;
};

function int2word(integer) {
    integer = integer.toString(16);
    if (integer.length % 8 !== 0) {
        while (integer.length < 8) {
            integer = '0' + integer;
        }
    }
    return integer;
};

function int2longword(integer) {
    integer = integer.toString(16);
    if (integer.length % 16 !== 0) {
        while (integer.length < 16) {
            integer = '0' + integer;
        }
    }
    return integer;
};



var bs58 = require("./bs58");
var decoders = {
    "base58": function(input) {
        return bs58.decode(input);
    }
}
var encoders = {
    "base58": function(input) {
        return bs58.encode(input);
    }
}

BP.toBase58 = function() {
    return encoders.base58(this);
}
BP.toBase64 = function() {
    return this.toString('base64');
}






for (var p in BP) {
    Buffer.prototype[p] = BP[p];
}




var encodings = ["ascii", "utf8", "utf16le", "ucs2", "base64", "binary", "hex"];
var specialencodings = ["base58"];

function nb(a, encoding, minsize) {
    if (typeof(a) === "object") {
        if (typeof(a.bitSize) === "function") {
            return a;
        }
    }
    var holder;

    if (!encoding && !minsize && typeof(a) === "number") {
        holder = new Buffer(a);
    } else {


        if (!encoding) {
            encoding = 'utf8';
        }
        if (encodings.indexOf(encoding) > -1) {
            holder = new Buffer(a + "", encoding);
            if (typeof(a) === "number") {
                holder.fill(0);
            }
            if (minsize) {
                var hholder = new Buffer(minsize);
                hholder.fill(0)
                holder.copy(hholder, hholder.length - holder.length);
                holder = hholder;
            }

        } else {
            if (specialencodings.indexOf(encoding) > -1 && decoders[encoding]) {
                var tt = decoders[encoding](a);
                holder = new Buffer(tt);
            } else {

                var base = parseInt(encoding);
                var bb = bigi.str2bigInt(a, base);
                var ss = bigi.bigInt2str(bb, 16);

                holder = new Buffer(ss.length % 2 === 0 ? ss : "0" + ss, 'hex');

                if (minsize) {
                    var hholder = new Buffer(minsize);
                    hholder.fill(0)
                    holder.copy(hholder, hholder.length - holder.length);
                    holder = hholder;
                }

            }





        }

    }


    if (Buffer._useTypedArrays) {
        for (var p in BP) {
            holder[p] = BP[p];
        }
    }
    return holder;
}


module.exports = nb;


/*

'ascii' - for 7 bit ASCII data only. This encoding method is very fast, and will strip the high bit if set.
Note that when converting from string to buffer, this encoding converts a null character ('\0' or '\u0000') into 0x20 (character code of a space). If you want to convert a null character into 0x00, you should use 'utf8'.

'utf8' - Multibyte encoded Unicode characters. Many web pages and other document formats use UTF-8.
'utf16le' - 2 or 4 bytes, little endian encoded Unicode characters. Surrogate pairs (U+10000 to U+10FFFF) are supported.
'ucs2' - Alias of 'utf16le'.
'base64' - Base64 string encoding.
'binary' - A way of encoding raw binary data into strings by using only the first 8 bits of each character. This encoding method is deprecated and should be avoided in favor of Buffer objects where possible. This encoding will be removed in future versions of Node.
'hex' - Encode each byte as two hexadecimal characters.
*/