"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SHAMap = exports.Leaf = exports.InnerNode = exports.Node = exports.NodeType = void 0;
const hash_prefix_1 = __importDefault(require("./hash-prefix"));
const sha512Half_1 = __importDefault(require("./sha512Half"));
const HEX_ZERO = '0000000000000000000000000000000000000000000000000000000000000000';
var NodeType;
(function (NodeType) {
    NodeType[NodeType["INNER"] = 1] = "INNER";
    NodeType[NodeType["TRANSACTION_NO_METADATA"] = 2] = "TRANSACTION_NO_METADATA";
    NodeType[NodeType["TRANSACTION_METADATA"] = 3] = "TRANSACTION_METADATA";
    NodeType[NodeType["ACCOUNT_STATE"] = 4] = "ACCOUNT_STATE";
})(NodeType = exports.NodeType || (exports.NodeType = {}));
class Node {
    constructor() { }
    addItem(_tag, _node) {
        throw new Error('Called unimplemented virtual method SHAMapTreeNode#addItem.');
    }
    get hash() {
        throw new Error('Called unimplemented virtual method SHAMapTreeNode#hash.');
    }
}
exports.Node = Node;
class InnerNode extends Node {
    constructor(depth = 0) {
        super();
        this.leaves = {};
        this.type = NodeType.INNER;
        this.depth = depth;
        this.empty = true;
    }
    addItem(tag, node) {
        const existingNode = this.getNode(parseInt(tag[this.depth], 16));
        if (existingNode) {
            if (existingNode instanceof InnerNode) {
                existingNode.addItem(tag, node);
            }
            else if (existingNode instanceof Leaf) {
                if (existingNode.tag === tag) {
                    throw new Error('Tried to add a node to a SHAMap that was already in there.');
                }
                else {
                    const newInnerNode = new InnerNode(this.depth + 1);
                    newInnerNode.addItem(existingNode.tag, existingNode);
                    newInnerNode.addItem(tag, node);
                    this.setNode(parseInt(tag[this.depth], 16), newInnerNode);
                }
            }
        }
        else {
            this.setNode(parseInt(tag[this.depth], 16), node);
        }
    }
    setNode(slot, node) {
        if (slot < 0 || slot > 15) {
            throw new Error('Invalid slot: slot must be between 0-15.');
        }
        this.leaves[slot] = node;
        this.empty = false;
    }
    getNode(slot) {
        if (slot < 0 || slot > 15) {
            throw new Error('Invalid slot: slot must be between 0-15.');
        }
        return this.leaves[slot];
    }
    get hash() {
        if (this.empty)
            return HEX_ZERO;
        let hex = '';
        for (let i = 0; i < 16; i++) {
            hex += this.leaves[i] ? this.leaves[i].hash : HEX_ZERO;
        }
        const prefix = hash_prefix_1.default.INNER_NODE.toString(16);
        return sha512Half_1.default(prefix + hex);
    }
}
exports.InnerNode = InnerNode;
class Leaf extends Node {
    constructor(tag, data, type) {
        super();
        this.tag = tag;
        this.type = type;
        this.data = data;
    }
    get hash() {
        switch (this.type) {
            case NodeType.ACCOUNT_STATE: {
                const leafPrefix = hash_prefix_1.default.LEAF_NODE.toString(16);
                return sha512Half_1.default(leafPrefix + this.data + this.tag);
            }
            case NodeType.TRANSACTION_NO_METADATA: {
                const txIDPrefix = hash_prefix_1.default.TRANSACTION_ID.toString(16);
                return sha512Half_1.default(txIDPrefix + this.data);
            }
            case NodeType.TRANSACTION_METADATA: {
                const txNodePrefix = hash_prefix_1.default.TRANSACTION_NODE.toString(16);
                return sha512Half_1.default(txNodePrefix + this.data + this.tag);
            }
            default:
                throw new Error('Tried to hash a SHAMap node of unknown type.');
        }
    }
}
exports.Leaf = Leaf;
class SHAMap {
    constructor() {
        this.root = new InnerNode(0);
    }
    addItem(tag, data, type) {
        this.root.addItem(tag, new Leaf(tag, data, type));
    }
    get hash() {
        return this.root.hash;
    }
}
exports.SHAMap = SHAMap;
//# sourceMappingURL=shamap.js.map