"use strict";
var util = require('../formatter');
const { Buffer } = require('buffer');
var PathSet = require('./path').PathSet;
var PathLeafElement = require('./path').PathLeafElement;
var EmptyLeaf = require('./binarytree').EmptyLeaf;
var Leaf = require('./binarytree').Leaf;
var Node = require('./binarytree').Node;
var BinaryTree = require('./binarytree').BinaryTree;
var ArrayHeadNode = require('./binarytree').ArrayHeadNode;
var DictHeadNode = require('./binarytree').DictHeadNode;
const NO_PATHS = new PathSet([]);
/**
 * The factory does the conversion between list of elements and tree of elements.
 *
 * Note: The idea is that you should sub class for each type of element you want to build.
 */
function BinaryTreeFactory() { }
/**
 * Transforms the incoming leaf into an [BinaryTreeElement]
 * The idea with this function is that it can be recursive (if the leaf in turn is complex object with sub objects).
 *
 * Note: If we don't have a path here we can try to find the leaf in the cache.
 *
 * @param leaf the raw data we should wrap in a leaf
 * @param paths a collection of proof paths that might point to this leaf
 * @return the resulting [BinaryTreeElement] the leaf got converted to
 */
BinaryTreeFactory.prototype.handleLeaf = function (leaf, paths, isRoot = false) {
    if (paths.length == 0 && !isRoot) {
        return this.innerHandleLeaf(leaf, this.getEmptyPathSet());
    }
    else {
        return this.innerHandleLeaf(leaf, paths);
    }
};
/**
 *
 */
BinaryTreeFactory.prototype.getEmptyPathSet = function () {
    return NO_PATHS;
};
/**
 * At this point we should have looked in cache.
 *
 * @param leaf we should turn into a tree element
 * @param {PathSet} paths
 * @return the tree element we created.
 */
BinaryTreeFactory.prototype.innerHandleLeaf = function (leaf, paths) {
    if (leaf == null) {
        return this.handlePrimitiveLeaf(leaf, paths);
    }
    if (Buffer.isBuffer(leaf)) {
        return this.handlePrimitiveLeaf(leaf, paths);
    }
    if (typeof leaf === 'string') {
        return this.handlePrimitiveLeaf(leaf, paths);
    }
    if (typeof leaf === 'number') {
        return this.handlePrimitiveLeaf(leaf, paths);
    }
    if (typeof leaf === 'bigint') {
        return this.handlePrimitiveLeaf(leaf, paths);
    }
    if (typeof leaf === 'boolean') {
        return this.handlePrimitiveLeaf(leaf ? 1 : 0, paths);
    }
    if (leaf.constructor === Array) {
        return this.buildFromArray(leaf, paths);
    }
    if (typeof leaf === 'object') {
        return this.buildFromDictionary(leaf, paths);
    }
    else {
        throw new Error("Unsupported data type");
    }
};
/**
 * Just like [handleLeaf] but we know that this leaf should not be a complex type, but something we can
 * immediately wrap
 *
 * @param leaf
 * @param {PathSet} paths
 */
BinaryTreeFactory.prototype.handlePrimitiveLeaf = function (leaf, paths) {
    var pathElem = paths.getPathLeafOrElseAnyCurrentPathElement();
    if (pathElem != null && !(pathElem instanceof PathLeafElement)) {
        throw new Error("Path does not match the tree structure. We are at a leaf " + leaf + " but found path element " + pathElem);
    }
    return new Leaf(leaf, pathElem);
};
/**
 * Calls itself until the return value only holds 1 element
 *
 * Note: This method can only create standard [Node] that fills up the area between the "top" and the leaves.
 *        These "in-between" nodes cannot be "path leaf" or have any interesting properties.
 *
 * @param layer What layer we aim calculate
 * @param inList The args of nodes we should build from
 * @return All [BinaryTreeElement] nodes of the next layer
 */
BinaryTreeFactory.prototype.buildHigherLayer = function (layer, inList) {
    if (inList.length === 0) {
        throw new Error("Cannot work on empty arrays. Layer: " + layer);
    }
    else if (inList.length === 1) {
        return inList;
    }
    var returnArray = new Array();
    var nrOfNodesToCreate = Math.floor(inList.length / 2);
    var leftValue = null;
    var isLeft = true;
    for (var i = 0; i < inList.length; i++) {
        if (isLeft) {
            leftValue = inList[i];
            isLeft = false;
        }
        else {
            var tempNode = new Node(leftValue, inList[i]);
            returnArray.push(tempNode);
            nrOfNodesToCreate--;
            isLeft = true;
            leftValue = null;
        }
    }
    if (!isLeft) {
        // If there is odd number of nodes, then move the last node up one level
        returnArray.push(leftValue);
    }
    // Extra check
    if (nrOfNodesToCreate != 0) {
        util.logDebug("Why didn't we build exactly the correct amount? Layer: " + layer + " , residue: " + nrOfNodesToCreate + " , input args size: " + inList.length + ".");
    }
    return this.buildHigherLayer((layer + 1), returnArray);
};
BinaryTreeFactory.prototype.build = function (data) {
    return this.buildWithPath(data, NO_PATHS);
};
/**
 * @param {PathSet} paths
 */
BinaryTreeFactory.prototype.buildWithPath = function (data, paths) {
    var result = this.handleLeaf(data, paths, true);
    return new BinaryTree(result);
};
/**
 * @param {Array} array
 * @param {PathSet} paths
 */
BinaryTreeFactory.prototype.buildFromArray = function (array, paths) {
    var pathElem = paths.getPathLeafOrElseAnyCurrentPathElement();
    // 1. Build leaf layer
    if (array.length == 0) {
        return new ArrayHeadNode(new EmptyLeaf(), new EmptyLeaf(), array, 0, 0, pathElem);
    }
    var leafArray = this.buildLeafElements(array, paths);
    // 2. Build all higher layers
    var result = this.buildHigherLayer(1, leafArray);
    // 3. Fix and return the root node
    var orgRoot = result[0];
    if (orgRoot instanceof Node) {
        return new ArrayHeadNode(orgRoot.left, orgRoot.right, array, array.length, pathElem);
    }
    if (orgRoot instanceof Leaf) {
        return this.buildFromOneLeaf(array, orgRoot, pathElem);
    }
    else {
        throw new Error("Should not find element of this type here");
    }
};
/**
 *
 */
BinaryTreeFactory.prototype.buildFromOneLeaf = function (array, orgRoot, pathElem) {
    if (array.length > 1) {
        throw new Error("How come we got a leaf returned when we had " + array.length + " elements is the args?");
    }
    else {
        return new ArrayHeadNode(orgRoot, new EmptyLeaf(), array, array.length, pathElem);
    }
};
/**
 * @param {PathSet} paths
 */
BinaryTreeFactory.prototype.buildLeafElements = function (leafList, paths) {
    var leafArray = new Array();
    var onlyArrayPaths = paths.keepOnlyArrayPaths(); // For performance, since we will loop soon
    for (var i = 0; i < leafList.length; i++) {
        var pathsRelevantForThisLeaf = onlyArrayPaths.getTailIfFirstElementIsArrayOfThisIndexFromList(i);
        var leaf = leafList[i];
        var binaryTreeElement = this.handleLeaf(leaf, pathsRelevantForThisLeaf);
        leafArray.push(binaryTreeElement);
    }
    return leafArray;
};
/**
 * @param {PathSet} paths
 */
BinaryTreeFactory.prototype.buildFromDictionary = function (dict, paths) {
    var pathElem = paths.getPathLeafOrElseAnyCurrentPathElement();
    // Needs to be sorted, or else the order is undefined
    var keys = Object.keys(dict).sort();
    if (keys.length == 0) {
        return new DictHeadNode(new EmptyLeaf(), new EmptyLeaf(), dict, keys.length, 0, pathElem);
    }
    // 1. Build first (leaf) layer
    var leafArray = this.buildLeafElementFromDict(keys, dict, paths);
    // 2. Build all higher layers
    var result = this.buildHigherLayer(1, leafArray);
    // 3. Fix and return the root node
    var orgRoot = result[0];
    if (orgRoot instanceof Node) {
        return new DictHeadNode(orgRoot.left, orgRoot.right, dict, keys.length, pathElem);
    }
    else {
        throw new Error("Should not find element of this type here: " + typeof orgRoot);
    }
};
/**
 * @param {PathSet} paths
 */
BinaryTreeFactory.prototype.buildLeafElementFromDict = function (keys, dict, paths) {
    var leafArray = new Array();
    var onlyDictPaths = paths.keepOnlyDictPaths(); // For performance, since we will loop soon
    for (var i = 0; i < keys.length; i++) {
        // The key cannot not be proved, so NO_PATHS
        var key = keys[i];
        var keyElement = this.handleLeaf(key, NO_PATHS);
        leafArray.push(keyElement);
        var content = dict[key];
        var pathsRelevantForThisLeaf = onlyDictPaths.getTailIfFirstElementIsDictOfThisKeyFromList(key);
        var contentElement = this.handleLeaf(content, pathsRelevantForThisLeaf);
        leafArray.push(contentElement);
    }
    return leafArray;
};
module.exports = { BinaryTreeFactory };
//# sourceMappingURL=binarytreefactory.js.map