"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExpressionTree = void 0;
const PredefinedTokenEnum_1 = require("./PredefinedTokenEnum");
const PredefinedTokens_1 = require("./PredefinedTokens");
const Tokenizer_1 = require("./Tokenizer");
const PredefinedType_1 = require("./PredefinedType");
const Node_1 = require("./Node");
const StringUtils_1 = require("../../Utilities/StringUtils");
/**
 * Class to generate an expression tree so that expression can be evaluated or parsed at a later date
 * @internal
 */
class ExpressionTree {
    /**
     * Create a new node of the expression tree
     */
    static createNewNode(value, type) {
        const newNode = new Node_1.Node();
        newNode.value = value;
        newNode.valueType = type;
        newNode.left = undefined;
        newNode.right = undefined;
        return newNode;
    }
    /**
     * Given an expression string, create an expression tree
     * @internal
     */
    static constructExpressionTree(expression) {
        if (!expression || !expression.trim()) {
            // caller to log info "Optional expression missing. Implicit expression will automatically apply." if returned null
            return undefined;
        }
        const opStack = [];
        const valueStack = [];
        // split into tokens
        const tokenList = Tokenizer_1.Tokenizer.getExpressionAsTokenList(expression);
        for (const token of tokenList) {
            const newNode = this.createNewNode(token[0], token[1]);
            switch (token[1]) {
                case PredefinedType_1.PredefinedType.openParenthesis:
                    opStack.push(newNode);
                    break;
                case PredefinedType_1.PredefinedType.closeParenthesis:
                    while (opStack.length > 0 && opStack[opStack.length - 1].valueType !== PredefinedType_1.PredefinedType.openParenthesis) {
                        const topOpNode = opStack.pop();
                        let valNodeRight;
                        let valNodeLeft;
                        if (valueStack.length > 0) {
                            valNodeRight = valueStack.pop();
                        }
                        if (valueStack.length > 0) {
                            valNodeLeft = (topOpNode.valueType !== PredefinedType_1.PredefinedType.notOperator) ? valueStack.pop() : undefined;
                        }
                        topOpNode.right = valNodeRight;
                        topOpNode.left = valNodeLeft;
                        valueStack.push(topOpNode);
                    }
                    // finally found open parenthesis
                    if (opStack.length > 0 && valueStack.length > 0) {
                        // traverse left most node and add "("
                        let currNode = valueStack[valueStack.length - 1];
                        while (currNode && currNode.left) {
                            currNode = currNode.left;
                        }
                        if (currNode) {
                            currNode.left = opStack.pop();
                        }
                        // traverse right most node and add ")"
                        currNode = valueStack[valueStack.length - 1];
                        while (currNode && currNode.right) {
                            currNode = currNode.right;
                        }
                        if (currNode) {
                            currNode.right = newNode;
                        }
                    }
                    break;
                case PredefinedType_1.PredefinedType.notOperator:
                case PredefinedType_1.PredefinedType.operator:
                    while (opStack.length > 0 && this.operatorPriority(opStack[opStack.length - 1].value) < this.operatorPriority(token[0])) {
                        const topOpNode = opStack.pop();
                        let valNodeRight;
                        let valNodeLeft;
                        if (valueStack.length > 0) {
                            valNodeRight = valueStack.pop();
                        }
                        if (valueStack.length > 0) {
                            valNodeLeft = (topOpNode.valueType !== PredefinedType_1.PredefinedType.notOperator) ? valueStack.pop() : undefined;
                        }
                        topOpNode.right = valNodeRight;
                        topOpNode.left = valNodeLeft;
                        valueStack.push(topOpNode);
                    }
                    opStack.push(newNode);
                    break;
                case PredefinedType_1.PredefinedType.constant:
                case PredefinedType_1.PredefinedType.token:
                case PredefinedType_1.PredefinedType.custom:
                default:
                    valueStack.push(newNode);
            }
        }
        while (opStack.length > 0) {
            const topOpNode = opStack.pop();
            let valNodeRight;
            let valNodeLeft;
            if (valueStack.length > 0) {
                valNodeRight = valueStack.pop();
            }
            if (valueStack.length > 0) {
                valNodeLeft = (topOpNode.valueType !== PredefinedType_1.PredefinedType.notOperator) ? valueStack.pop() : undefined;
            }
            topOpNode.right = valNodeRight;
            topOpNode.left = valNodeLeft;
            valueStack.push(topOpNode);
        }
        return valueStack.pop();
    }
    /**
     * Order of operators
     * Higher the priority - higher the precedence
     */
    static operatorPriority(op) {
        if (!ExpressionTree.textToTokenHash.has(op)) {
            return 0;
        }
        else {
            switch (ExpressionTree.textToTokenHash.get(op)) {
                case PredefinedTokenEnum_1.PredefinedTokenEnum.OPENPAREN:
                case PredefinedTokenEnum_1.PredefinedTokenEnum.CLOSEPAREN:
                    return 4;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.NOT:
                    return 3;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.AND:
                case PredefinedTokenEnum_1.PredefinedTokenEnum.OR:
                    return 2;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.GT:
                case PredefinedTokenEnum_1.PredefinedTokenEnum.LT:
                case PredefinedTokenEnum_1.PredefinedTokenEnum.EQ:
                case PredefinedTokenEnum_1.PredefinedTokenEnum.NE:
                case PredefinedTokenEnum_1.PredefinedTokenEnum.GE:
                case PredefinedTokenEnum_1.PredefinedTokenEnum.LE:
                    return 1;
                default:
                    return 0;
            }
        }
    }
    /**
     * Given a condition and the input values, evaluate the condition
     * @internal
     */
    static evaluateCondition(condition, input) {
        if (StringUtils_1.StringUtils.isNullOrWhiteSpace(condition)) {
            return true;
        }
        const treeRoot = this.constructExpressionTree(condition);
        return this.evaluateExpressionTree(treeRoot, input);
    }
    /**
     * Given an expression tree, evaluate the expression
     * @internal
     */
    static evaluateExpressionTree(top, input) {
        if (!top) {
            return false;
        }
        let leftReturn = false;
        let rightReturn = false;
        if (top.left) {
            leftReturn = this.evaluateExpressionTree(top.left, input);
        }
        if (top.right) {
            rightReturn = this.evaluateExpressionTree(top.right, input);
        }
        if (top.valueType === PredefinedType_1.PredefinedType.custom) {
            // check if number and return number
            const num = Number(top.value);
            if (!isNaN(num)) {
                return num;
            }
            // check if bool and return bool
            if (typeof top.value === 'string') {
                if (top.value.trim().toLowerCase() === 'true') {
                    return true;
                }
                else if (top.value.trim().toLowerCase() === 'false') {
                    return false;
                }
            }
        }
        if (!ExpressionTree.textToTokenHash.has(top.value)) {
            return top.value;
        }
        else {
            switch (ExpressionTree.textToTokenHash.get(top.value)) {
                case PredefinedTokenEnum_1.PredefinedTokenEnum.AND:
                    return (leftReturn === undefined || rightReturn === undefined) ? false : leftReturn && rightReturn;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.NOT:
                    return (rightReturn === undefined) ? false : !rightReturn;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.OR:
                    return (leftReturn === undefined || rightReturn === undefined) ? false : leftReturn || rightReturn;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.GT:
                    return (leftReturn === undefined || rightReturn === undefined) ? false : leftReturn > rightReturn;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.LT:
                    return (leftReturn === undefined || rightReturn === undefined) ? false : leftReturn < rightReturn;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.GE:
                    return (leftReturn === undefined || rightReturn === undefined) ? false : leftReturn >= rightReturn;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.LE:
                    return (leftReturn === undefined || rightReturn === undefined) ? false : leftReturn <= rightReturn;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.EQ:
                    return (leftReturn === undefined || rightReturn === undefined) ? false : leftReturn === rightReturn;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.NE:
                    return (leftReturn === undefined || rightReturn === undefined) ? false : leftReturn !== rightReturn;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.TRUE:
                    return true;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.FALSE:
                    return false;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.OPENPAREN:
                case PredefinedTokenEnum_1.PredefinedTokenEnum.CLOSEPAREN:
                    return true;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.DEPTH:
                    return input.nextDepth;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.MAXDEPTH:
                    return input.maxDepth;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.ISARRAY:
                    return input.isArray;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.NOMAXDEPTH:
                    return input.noMaxDepth;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.MINCARDINALITY:
                    return input.minCardinality;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.MAXCARDINALITY:
                    return input.maxCardinality;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.NORMALIZED:
                    return input.normalized;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.REFERENCEONLY:
                    return input.referenceOnly;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.STRUCTURED:
                    return input.structured;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.VIRTUAL:
                    return input.isVirtual;
                case PredefinedTokenEnum_1.PredefinedTokenEnum.ALWAYS:
                    return true;
                default:
                    return top.value;
            }
        }
    }
    /**
     * For unit test only
     * @internal
     */
    static inOrderTraversal(top) {
        if (top) {
            if (top.left) {
                this.inOrderTraversal(top.left);
            }
            console.log(` ${top.value} `);
            if (top.right) {
                this.inOrderTraversal(top.right);
            }
        }
    }
}
exports.ExpressionTree = ExpressionTree;
/**
 * @internal
 */
ExpressionTree.textToTokenHash = PredefinedTokens_1.PredefinedTokens.initializeTextToTokenHash();
//# sourceMappingURL=ExpressionTree.js.map