/**
 * PKCS1 padding and signature scheme
 */

var BigInteger = require('../libs/jsbn');
var CryptoJS = require('../../crypto-js/crypto-js');
var constants = { RSA_NO_PADDING: 3 }
require('constants');
var SIGN_INFO_HEAD = {
    md2: Buffer.from('3020300c06082a864886f70d020205000410', 'hex'),
    md5: Buffer.from('3020300c06082a864886f70d020505000410', 'hex'),
    sha1: Buffer.from('3021300906052b0e03021a05000414', 'hex'),
    sha224: Buffer.from('302d300d06096086480165030402040500041c', 'hex'),
    sha256: Buffer.from('3031300d060960864801650304020105000420', 'hex'),
    sha384: Buffer.from('3041300d060960864801650304020205000430', 'hex'),
    sha512: Buffer.from('3051300d060960864801650304020305000440', 'hex'),
    ripemd160: Buffer.from('3021300906052b2403020105000414', 'hex'),
    rmd160: Buffer.from('3021300906052b2403020105000414', 'hex')
};

var SIGN_ALG_TO_HASH_ALIASES = {
    'ripemd160': 'rmd160'
};

var DEFAULT_HASH_FUNCTION = 'sha256';

module.exports = {
    isEncryption: true,
    isSignature: true
};

module.exports.makeScheme = function (key, options) {
    function Scheme(key, options) {
        this.key = key;
        this.options = options;
    }

    Scheme.prototype.maxMessageLength = function () {
        if (this.options.encryptionSchemeOptions && this.options.encryptionSchemeOptions.padding == constants.RSA_NO_PADDING) {
            return this.key.encryptedDataLength;
        }
        return this.key.encryptedDataLength - 11;
    };

    /**
     * Unpad input Buffer and, if valid, return the Buffer object
     * alg: PKCS#1 (type 2, random)
     * @param buffer
     * @returns {Buffer}
     */
    Scheme.prototype.encUnPad = function (buffer, options) {
        options = options || {};
        var i = 0;

        if (this.options.encryptionSchemeOptions && this.options.encryptionSchemeOptions.padding == constants.RSA_NO_PADDING) {
            //RSA_NO_PADDING treated like JAVA left pad with zero character
            var unPad;
            if (typeof buffer.lastIndexOf == "function") { //patch for old node version
                unPad = buffer.slice(buffer.lastIndexOf('\0') + 1, buffer.length);
            } else {
                unPad = buffer.slice(String.prototype.lastIndexOf.call(buffer, '\0') + 1, buffer.length);
            }
            return unPad;
        }

        if (buffer.length < 4) {
            return null;
        }

        /* Type 1: zeros padding for private key decrypt */
        if (options.type === 1) {
            if (buffer[0] !== 0 && buffer[1] !== 1) {
                return null;
            }
            i = 3;
            while (buffer[i] !== 0) {
                if (buffer[i] != 0xFF || ++i >= buffer.length) {
                    return null;
                }
            }
        } else {
            /* random padding for public key decrypt */
            if (buffer[0] !== 0 && buffer[1] !== 2) {
                return null;
            }
            i = 3;
            while (buffer[i] !== 0) {
                if (++i >= buffer.length) {
                    return null;
                }
            }
        }
        return buffer.slice(i + 1, buffer.length);
    };

    Scheme.prototype.sign = function (buffer) {
        var hashAlgorithm = this.options.signingSchemeOptions.hash || DEFAULT_HASH_FUNCTION;
        if (this.options.environment === 'browser') {
            hashAlgorithm = SIGN_ALG_TO_HASH_ALIASES[hashAlgorithm] || hashAlgorithm;
            var hasher = CryptoJS[hashAlgorithm.toUpperCase()] (buffer.toString());
            var asBuffer = wordArrayToBuffer(hasher)
            var paddedHash = this.pkcs1pad(asBuffer, hashAlgorithm);
            var res = this.key.$doPrivate(new BigInteger(paddedHash)).toBuffer(this.key.encryptedDataLength);
            return res;
        } else {
            throw new Error ('CCXT only supports browser mode :P');
        }
    };

    /**
     * PKCS#1 pad input buffer to max data length
     * @param hashBuf
     * @param hashAlgorithm
     * @returns {*}
     */
    Scheme.prototype.pkcs1pad = function (hashBuf, hashAlgorithm) {
        var digest = SIGN_INFO_HEAD[hashAlgorithm];
        if (!digest) {
            throw Error('Unsupported hash algorithm');
        }

        var data = Buffer.concat([digest, hashBuf]);

        if (data.length + 10 > this.key.encryptedDataLength) {
            throw Error('Key is too short for signing algorithm (' + hashAlgorithm + ')');
        }

        var filled = Buffer.alloc(this.key.encryptedDataLength - data.length - 1);
        filled.fill(0xff, 0, filled.length - 1);
        filled[0] = 1;
        filled[filled.length - 1] = 0;

        var res = Buffer.concat([filled, data]);

        return res;
    };

    return new Scheme(key, options);
};

// used to convert `CryptoJS` wordArrays into `crypto` hex buffers
function wordToByteArray(word, length) {
    var ba = [],
        xFF = 0xFF;
    if (length > 0)
        ba.push(word >>> 24);
    if (length > 1)
        ba.push((word >>> 16) & xFF);
    if (length > 2)
        ba.push((word >>> 8) & xFF);
    if (length > 3)
        ba.push(word & xFF);

    return ba;
}

function wordArrayToBuffer(wordArray) {
    let length = undefined;
    if (wordArray.hasOwnProperty("sigBytes") && wordArray.hasOwnProperty("words")) {
        length = wordArray.sigBytes;
        wordArray = wordArray.words;
    } else {
        throw Error('Argument not a wordArray')
    }

    const result = []
    let bytes = []
    let i = 0;
    while (length > 0) {
        bytes = wordToByteArray(wordArray[i], Math.min(4, length));
        length -= bytes.length;
        result.push(bytes);
        i++;
    }
    return new Buffer.from([].concat.apply([], result), 'hex');
}
