export function TreeNode(data, left = null, right = null) {
    this.data = data;
    this.left = left;
    this.right = right;
}

TreeNode.prototype = {
    getMaxChildrenAtDepth: function (death, current = 0) {
        if (current === death)
            return 1;

        let tmp = 0;
        if (this.right != null)
            tmp += this.right.getMaxChildrenAtDepth(death, current + 1);

        if (this.left != null)
            tmp += this.left.getMaxChildrenAtDepth(death, current + 1);

        return tmp;
    },
    death: function () {
        let dg = 0;
        let dd = 0;

        if (this.right != null)
            dg = this.right.death();

        if (this.left != null)
            dg = this.left.death();

        return 1 + Math.max(dg, dd);
    },
    isEnd: function (data) {
        if (this.data === data) {
            return this.right == null && this.left == null;
        } else {
            return (this.right != null && this.right.isEnd(data)) || (this.left != null && this.left.isEnd(data));
        }
    },
    flat: function () {
        let out = []
        this.__flat(out)
        return out;
    },
    __flat: function (out) {
        out.push(this.data)
        if (this.left != null)
            this.left.__flat(out)
        if (this.right != null)
            this.right.__flat(out)
    },
    nextMatchTree: function (trees) {
        const tmpData = {keep_death: -1, keep_data: null}
        for (const treeNode of trees)
            this.__nextMatchTree(treeNode, 0, tmpData)
        return tmpData.keep_data;
    },
    __nextMatchTree: function (treeNode, death, tmpData) {
        if (treeNode == null)
            return;

        const match = treeNode.data
        if (!match.end && death > tmpData.keep_death && this.data !== match.id) {
            tmpData.keep_death = death;
            tmpData.keep_data = match.id;
        }

        this.__nextMatchTree(treeNode.left, death + 1, tmpData);
        this.__nextMatchTree(treeNode.right, death + 1, tmpData);
    }
}

export function build_tree(nb_comb, sub_tree_level = 0) {
    const nb_match = Math.ceil(nb_comb / 2)
    const level = Math.ceil(Math.log(nb_match) / Math.log(2))

    const treeNodes = []

    for (let i = level; i >= 0; i--) {
        const treeNode = new TreeNode(null)
        build_tree_(treeNode, 0, i, 0, nb_match)
        treeNodes.push(treeNode)

        if (sub_tree_level !== -1) {
            if (sub_tree_level === 0)
                break
            if (i > sub_tree_level)
                i = sub_tree_level
        }
    }

    return treeNodes
}

export function build_tree_(treeNode, death, death_target, leave, leave_target) {
    if (death === death_target) {
        return (leave < leave_target) ? 1 : 0;
    }

    if (leave + 1 === leave_target) {
        return 1;
    } else {
        let to_add = 0;
        if (leave < leave_target) {
            treeNode.left = new TreeNode(null)
            to_add += build_tree_(treeNode.left, death + 1, death_target, leave, leave_target)
        }

        if (leave + to_add < leave_target) {
            treeNode.right = new TreeNode(null)
            to_add += build_tree_(treeNode.right, death + 1, death_target, leave + to_add, leave_target)
        }

        return to_add;
    }
}

export function resize_tree(src, to) {
    if (src === null || to === null)
        return null;

    if (src.left === null && to.left != null) {
        src.left = to.left;
    } else if (src.left != null && to.left === null) {
        src.left = null;
    } else {
        resize_tree(src.left, to.left);
    }

    if (src.right === null && to.right != null) {
        src.right = to.right;
    } else if (src.right != null && to.right === null) {
        src.right = null;
    } else {
        resize_tree(src.right, to.right);
    }
}

export function from_sendTree(tree, matchId = true) {
    if (tree == null)
        return null;

    const node = new TreeNode(matchId ? tree.match?.id : tree.match);
    node.left = from_sendTree(tree.left, matchId);
    node.right = from_sendTree(tree.right, matchId);

    return node;
}
