var util = require('./util'),
    conv = require('./convert'),
    ECKey = require('./eckey').ECKey,
    ECPubKey = require('./eckey').ECPubKey,
    base58 = require('./base58'),
    Crypto = require('./crypto-js/crypto');

var BIP32key = function(opts) {
    if (!opts) opts = {}
    if (typeof opts == 'string') {
        try {
            opts = BIP32key.deserialize(opts);
        }
        catch(e) {
            opts = BIP32key.fromMasterKey(opts);
        }
    }
    this.vbytes = opts.vbytes;
    this.depth = opts.depth;
    this.fingerprint = opts.fingerprint;
    this.i = opts.i;
    this.chaincode = opts.chaincode;
    this.key = opts.key;
    this.type = conv.bytesToString(this.vbytes) == PRIVDERIV ? 'priv' : 'pub'
    return this;
}

var PRIVDERIV = BIP32key.PRIVDERIV = '\x04\x88\xAD\xE4'
var PUBDERIV = BIP32key.PUBDERIV = '\x04\x88\xB2\x1E'

BIP32key.deserialize = function(str) {
    var bytes = base58.decode(str)
    var front = bytes.slice(0,bytes.length-4),
        back = bytes.slice(bytes.length-4);
    var checksum = Crypto.SHA256(Crypto.SHA256(front,{asBytes: true}), {asBytes: true})
                        .slice(0,4);
    if ('' + checksum != '' + back) {
        throw new Error('Checksum failed');
    }
    var type = conv.bytesToString(bytes.slice(0,4)) == PRIVDERIV ? 'priv' : 'pub';
    return new BIP32key({
        type: type,
        vbytes: bytes.slice(0,4),
        depth: bytes[4],
        fingerprint: bytes.slice(5,9),
        i: util.bytesToNum(bytes.slice(9,13).reverse()),
        chaincode: bytes.slice(13,45),
        key: type == 'priv' ? new ECKey(bytes.slice(46,78).concat([1]),true)
                            : new ECPubKey(bytes.slice(45,78),true)
    })
}

BIP32key.prototype.serialize = function() {
    var bytes = this.vbytes.concat(
                [this.depth],
                this.fingerprint,
                util.numToBytes(this.i,4).reverse(),
                this.chaincode,
                this.type == 'priv' ? [0].concat(this.key.export('bytes').slice(0,32))
                                    : this.key.export('bytes'))
    var checksum = Crypto.SHA256(Crypto.SHA256(bytes,{asBytes: true}), {asBytes: true})
                         .slice(0,4)
    return base58.encode(bytes.concat(checksum))
}

BIP32key.prototype.ckd = function(i) {
    var priv, pub, newkey, fingerprint, blob, I;
    if (this.type == 'priv') {
        priv = this.key.export('bytes')
        pub = this.key.getPub().export('bytes')
    }
    else pub = this.key.export('bytes')

    if (i >= 2147483648) {
        if (!priv) throw new Error("Can't do private derivation on public key!")
        blob = [0].concat(priv.slice(0,32),util.numToBytes(i,4).reverse())
    }
    else blob = pub.concat(util.numToBytes(i,4).reverse())

    I = Crypto.HMAC(Crypto.SHA512,blob,this.chaincode,{ asBytes: true })

    if (this.type == 'priv') {
        newkey = this.key.add(ECKey(I.slice(0,32).concat([1])))
        fingerprint = util.sha256ripe160(this.key.getPub().export('bytes')).slice(0,4)
    }
    else {
        newkey = this.key.add(ECKey(I.slice(0,32).concat([1])).getPub());
        fingerprint = util.sha256ripe160(this.key.export('bytes')).slice(0,4)
    }
    return new BIP32key({
        vbytes: this.vbytes,
        type: this.type,
        depth: this.depth + 1,
        fingerprint: fingerprint,
        i: i,
        chaincode: I.slice(32),
        key: newkey
    })
}

BIP32key.prototype.clone = function() {
    return new BIP32key(this);
}

BIP32key.prototype.privtopub = BIP32key.prototype.getPub = function() {
    if (this.type == 'pub') return this.clone()
    return new BIP32key({
        vbytes: conv.stringToBytes(PUBDERIV),
        type: 'pub',
        depth: this.depth,
        fingerprint: this.fingerprint,
        i: this.i,
        chaincode: this.chaincode,
        key: this.key.getPub()
    })
}

BIP32key.fromMasterKey = function(seed) {
    var I = Bitcoin.Crypto.HMAC(Bitcoin.Crypto.SHA512,seed, 'Bitcoin seed' , { asBytes: true })
    return new BIP32key({
        vbytes: conv.stringToBytes(PRIVDERIV),
        type: 'priv',
        depth: 0,
        fingerprint: [0,0,0,0],
        i: 0,
        chaincode: I.slice(32),
        key: new ECKey(I.slice(0,32).concat([1]),true)
    })
}

BIP32key.prototype.getKey = function() { return this.key }

module.exports = BIP32key;