"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateLeaf = exports.addLeaf = exports.verifyLeaf = exports.getMerkleRootByPath = exports.getMerklePath = exports.merkelizeArray = exports.getMerkleRoot = void 0;
const sha_1 = require("./sha");
const lt_1 = __importDefault(require("semver/functions/lt"));
const contracts_1 = require("./contracts");
function getMerkleRoot(hashes) {
    let array = hashes;
    do {
        array = merkelizeArray(array);
    } while (array.length > 1);
    return array[0];
}
exports.getMerkleRoot = getMerkleRoot;
function merkelizeArray(array) {
    const reduced = [];
    for (let i = 0; i <= array.length - 2; i++) {
        if (i % 2 === 0) {
            const hashString = array.slice(i, i + 2).join("");
            const newHash = (0, sha_1.sha256)(hashString);
            reduced.push(newHash);
        }
    }
    if (array.length % 2 == 1)
        reduced.push((0, sha_1.sha256)(array[array.length - 1].repeat(2)));
    return reduced;
}
exports.merkelizeArray = merkelizeArray;
function getMerklePath(pos, hashes) {
    const left = pos % 2 === 0 ? true : false;
    let sibling = left ? hashes[pos + 1] : hashes[pos - 1];
    sibling = sibling || hashes[pos];
    const path = left ? sibling + "01" : sibling + "00";
    // console.log(hashes.length, pos, left, left ? `(${hashes[pos]}) + ${sibling}` : `${sibling} + (${hashes[pos]})`)
    if (hashes.length <= 2)
        return path;
    return path + getMerklePath(Math.floor(pos / 2), merkelizeArray(hashes));
}
exports.getMerklePath = getMerklePath;
function getMerkleRootByPath(leaf, path) {
    const merklePathLength = path.length / 66;
    let i = 0;
    let merkleValue = leaf;
    while (i < merklePathLength) {
        const left = Boolean(parseInt(path.substring(i * 66 + 64, i * 66 + 66)));
        const sibling = path.substring(i * 66, i * 66 + 64);
        const merged = left ? merkleValue + sibling : sibling + merkleValue;
        // console.log(merged.substring(0, 64) + " + " + merged.substring(64, 128))
        merkleValue = (0, sha_1.sha256)(merged);
        // console.log("= " + merkleValue)
        i++;
    }
    return merkleValue;
}
exports.getMerkleRootByPath = getMerkleRootByPath;
function verifyLeaf(leaf, merklePath, merkleRoot) {
    const firstNeighbor = merklePath.substring(0, 64);
    if (firstNeighbor === leaf) {
        // Make sure that leaf is not just a mirror
        const firstLeft = Boolean(parseInt(merklePath.substring(64, 66)));
        if (!firstLeft)
            return false;
    }
    return getMerkleRootByPath(leaf, merklePath) === merkleRoot;
}
exports.verifyLeaf = verifyLeaf;
/**
  *  Makes sure that
  *  - the new leaf is added at the same depth as the other leafs.
  +  - the new leaf is hashed twice before adding.
  */
function addLeaf(lastEntry, lastMerklePath, oldMerkleRoot, newEntry, version = contracts_1.currentMarketContract) {
    const lastLeaf = (0, lt_1.default)(version.version, "0.6.5") ? (0, sha_1.sha256)(lastEntry) : (0, sha_1.sha256)((0, sha_1.sha256)(lastEntry));
    const newLeaf = (0, lt_1.default)(version.version, "0.6.5") ? (0, sha_1.sha256)(newEntry) : (0, sha_1.sha256)((0, sha_1.sha256)(newEntry));
    if (!verifyLeaf(lastLeaf, lastMerklePath, oldMerkleRoot))
        throw new Error("Invalid merkle root or path");
    const depth = lastMerklePath.length / 66;
    let i = 0;
    let merkleValue = newLeaf;
    let lastLeafValue = lastLeaf;
    let joined = false;
    while (i < depth) {
        const sibling = lastMerklePath.slice(i * 66, i * 66 + 64);
        const left = Boolean(parseInt(lastMerklePath.substring(i * 66 + 64, i * 66 + 66)));
        if (left) {
            if (!joined) {
                if (sibling !== lastLeafValue)
                    throw new Error("Invalid merkle path for last leaf");
                merkleValue = (0, sha_1.sha256)(lastLeafValue + merkleValue);
            }
            else {
                // if (sibling !== merkleValue) throw new Error("Invalid merkle path for last leaf")
                merkleValue = (0, sha_1.sha256)(merkleValue.repeat(2));
            }
            joined = true;
        }
        else {
            if (!joined) {
                merkleValue = (0, sha_1.sha256)(merkleValue.repeat(2));
                lastLeafValue = (0, sha_1.sha256)(sibling + lastLeafValue);
            }
            else {
                merkleValue = (0, sha_1.sha256)(sibling + merkleValue);
            }
        }
        i++;
    }
    if (!joined) {
        merkleValue = (0, sha_1.sha256)(oldMerkleRoot + merkleValue);
    }
    return merkleValue;
}
exports.addLeaf = addLeaf;
function updateLeaf(oldEntry, newEntry, merklePath, oldMerkleRoot, version = contracts_1.currentMarketContract) {
    // Make sure that entries are hashed twice
    const oldLeaf = (0, lt_1.default)(version.version, "0.6.5") ? (0, sha_1.sha256)(oldEntry) : (0, sha_1.sha256)((0, sha_1.sha256)(oldEntry));
    const newLeaf = (0, lt_1.default)(version.version, "0.6.5") ? (0, sha_1.sha256)(newEntry) : (0, sha_1.sha256)((0, sha_1.sha256)(newEntry));
    let i = 0;
    const merklePathLength = merklePath.length / 66;
    let oldMerkleValue = oldLeaf;
    let newMerkleValue = newLeaf;
    while (i < merklePathLength) {
        const left = parseInt(merklePath.slice(i * 66 + 64, i * 66 + 66));
        const oldNeighbor = merklePath.slice(i * 66, i * 66 + 64);
        const newNeighbor = oldNeighbor === oldMerkleValue ? newMerkleValue : oldNeighbor;
        // console.log(oldMerkleValue)
        if (left) {
            oldMerkleValue = (0, sha_1.sha256)(oldMerkleValue + oldNeighbor);
            newMerkleValue = (0, sha_1.sha256)(newMerkleValue + newNeighbor);
        }
        else {
            oldMerkleValue = (0, sha_1.sha256)(oldNeighbor + oldMerkleValue);
            newMerkleValue = (0, sha_1.sha256)(newNeighbor + newMerkleValue);
        }
        // console.log(oldNeighbor, "=>", oldMerkleValue, Boolean(left))
        i = i + 1;
    }
    if (oldMerkleValue !== oldMerkleRoot)
        throw new Error();
    return newMerkleValue;
}
exports.updateLeaf = updateLeaf;
//# sourceMappingURL=merkleTree.js.map