"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.hashStateTree = exports.hashTxTree = exports.hashLedgerHeader = exports.hashSignedTx = void 0;
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const ripple_binary_codec_1 = require("ripple-binary-codec");
const errors_1 = require("../../errors");
const HashPrefix_1 = __importDefault(require("./HashPrefix"));
const sha512Half_1 = __importDefault(require("./sha512Half"));
const SHAMap_1 = __importStar(require("./SHAMap"));
const HEX = 16;
function intToHex(integer, byteLength) {
    const foo = Number(integer)
        .toString(HEX)
        .padStart(byteLength * 2, '0');
    return foo;
}
function bytesToHex(bytes) {
    return Buffer.from(bytes).toString('hex');
}
function bigintToHex(integerString, byteLength) {
    const hex = new bignumber_js_1.default(integerString).toString(HEX);
    return hex.padStart(byteLength * 2, '0');
}
function addLengthPrefix(hex) {
    const length = hex.length / 2;
    if (length <= 192) {
        return bytesToHex([length]) + hex;
    }
    if (length <= 12480) {
        const prefix = length - 193;
        return bytesToHex([193 + (prefix >>> 8), prefix & 0xff]) + hex;
    }
    if (length <= 918744) {
        const prefix = length - 12481;
        return (bytesToHex([
            241 + (prefix >>> 16),
            (prefix >>> 8) & 0xff,
            prefix & 0xff,
        ]) + hex);
    }
    throw new errors_1.XrplError('Variable integer overflow.');
}
function hashSignedTx(tx) {
    let txBlob;
    let txObject;
    if (typeof tx === 'string') {
        txBlob = tx;
        txObject = (0, ripple_binary_codec_1.decode)(tx);
    }
    else {
        txBlob = (0, ripple_binary_codec_1.encode)(tx);
        txObject = tx;
    }
    if (txObject.TxnSignature === undefined && txObject.Signers === undefined) {
        throw new errors_1.ValidationError('The transaction must be signed to hash it.');
    }
    const prefix = HashPrefix_1.default.TRANSACTION_ID.toString(16).toUpperCase();
    return (0, sha512Half_1.default)(prefix.concat(txBlob));
}
exports.hashSignedTx = hashSignedTx;
function hashLedgerHeader(ledgerHeader) {
    const prefix = HashPrefix_1.default.LEDGER.toString(HEX).toUpperCase();
    const ledger = prefix +
        intToHex(Number(ledgerHeader.ledger_index), 4) +
        bigintToHex(ledgerHeader.total_coins, 8) +
        ledgerHeader.parent_hash +
        ledgerHeader.transaction_hash +
        ledgerHeader.account_hash +
        intToHex(ledgerHeader.parent_close_time, 4) +
        intToHex(ledgerHeader.close_time, 4) +
        intToHex(ledgerHeader.close_time_resolution, 1) +
        intToHex(ledgerHeader.close_flags, 1);
    return (0, sha512Half_1.default)(ledger);
}
exports.hashLedgerHeader = hashLedgerHeader;
function hashTxTree(transactions) {
    var _a;
    const shamap = new SHAMap_1.default();
    for (const txJSON of transactions) {
        const txBlobHex = (0, ripple_binary_codec_1.encode)(txJSON);
        const metaHex = (0, ripple_binary_codec_1.encode)((_a = txJSON.metaData) !== null && _a !== void 0 ? _a : {});
        const txHash = hashSignedTx(txBlobHex);
        const data = addLengthPrefix(txBlobHex) + addLengthPrefix(metaHex);
        shamap.addItem(txHash, data, SHAMap_1.NodeType.TRANSACTION_METADATA);
    }
    return shamap.hash;
}
exports.hashTxTree = hashTxTree;
function hashStateTree(entries) {
    const shamap = new SHAMap_1.default();
    entries.forEach((ledgerEntry) => {
        const data = (0, ripple_binary_codec_1.encode)(ledgerEntry);
        shamap.addItem(ledgerEntry.index, data, SHAMap_1.NodeType.ACCOUNT_STATE);
    });
    return shamap.hash;
}
exports.hashStateTree = hashStateTree;
function computeTransactionHash(ledger, options) {
    const { transaction_hash } = ledger;
    if (!options.computeTreeHashes) {
        return transaction_hash;
    }
    if (ledger.transactions == null) {
        throw new errors_1.ValidationError('transactions is missing from the ledger');
    }
    const transactionHash = hashTxTree(ledger.transactions);
    if (transaction_hash !== transactionHash) {
        throw new errors_1.ValidationError('transactionHash in header' +
            ' does not match computed hash of transactions', {
            transactionHashInHeader: transaction_hash,
            computedHashOfTransactions: transactionHash,
        });
    }
    return transactionHash;
}
function computeStateHash(ledger, options) {
    const { account_hash } = ledger;
    if (!options.computeTreeHashes) {
        return account_hash;
    }
    if (ledger.accountState == null) {
        throw new errors_1.ValidationError('accountState is missing from the ledger');
    }
    const stateHash = hashStateTree(ledger.accountState);
    if (account_hash !== stateHash) {
        throw new errors_1.ValidationError('stateHash in header does not match computed hash of state');
    }
    return stateHash;
}
function hashLedger(ledger, options = {}) {
    const subhashes = {
        transaction_hash: computeTransactionHash(ledger, options),
        account_hash: computeStateHash(ledger, options),
    };
    return hashLedgerHeader(Object.assign(Object.assign({}, ledger), subhashes));
}
exports.default = hashLedger;
//# sourceMappingURL=hashLedger.js.map