"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const ast_1 = require("../ast");
const toCodeString_1 = __importDefault(require("../toCodeString"));
const common_1 = require("../common");
const traverse_1 = require("@glas/traverse");
const normalize_1 = __importDefault(require("./normalize"));
const splitExpressions_1 = __importDefault(require("./splitExpressions"));
function find(items, predicate) {
    for (let item of items) {
        if (predicate(item)) {
            return item;
        }
    }
    return null;
}
function equals(a, b) {
    return toCodeString_1.default(a) === toCodeString_1.default(b);
}
// A && B || A => A
const simplify = common_1.memoize(function (e) {
    e = normalize_1.default(e);
    if (ast_1.TypeExpression.is(e)) {
        let value = simplify(e.value);
        if (e.value !== value) {
            e = e.patch({ value });
        }
    }
    if (ast_1.BinaryExpression.is(e)) {
        const left = simplify(e.left);
        const right = simplify(e.right);
        if (equals(left, right)) {
            if (e.operator === "&&" || e.operator == "||" || e.operator === "&" || e.operator == "|") {
                //  A && A => A
                //  A || A => A
                //  A &  A => A
                //  A |  A => A
                return left;
            }
        }
        else if (e.operator === "||") {
            if (find(splitExpressions_1.default(left, "&&"), c => equals(c, right))) {
                // A && B || A => A
                return right;
            }
            if (find(splitExpressions_1.default(right, "&&"), c => equals(c, left))) {
                //  A || A && B => A
                return left;
            }
            if (find(splitExpressions_1.default(left, "||"), c => equals(c, right))) {
                // (A || B) || A => A || B
                return left;
            }
            if (find(splitExpressions_1.default(right, "||"), c => equals(c, left))) {
                //  A || (A && B) => A || B
                return right;
            }
        }
        else if (e.operator === "&&") {
            for (let c of splitExpressions_1.default(left, "||")) {
                if (equals(c, right)) {
                    // (A || B) && A => A
                    return right;
                }
                if (ast_1.UnaryExpression.is(right) && right.operator === "!" && equals(c, right.argument)) {
                    //  (A || B) && !A => B
                    //  (A || B || C) && !A => B || C
                    return traverse_1.traverse(left, {
                        // find and remove the impossible clause
                        leave(node) {
                            if (ast_1.BinaryExpression.is(node) && node.operator === "||") {
                                if (equals(node.left, right.argument)) {
                                    return node.right;
                                }
                                if (equals(node.right, right.argument)) {
                                    return node.left;
                                }
                            }
                        }
                    });
                }
            }
            for (let c of splitExpressions_1.default(right, "||")) {
                if (equals(c, left)) {
                    // A && (A || B) => A
                    return left;
                }
            }
        }
        // should be normalized
        if (e.left !== left || e.right !== right) {
            e = e.patch({ left, right });
        }
    }
    else if (ast_1.UnaryExpression.is(e)) {
        let argument = simplify(e.argument);
        if (e.operator === "!")
            if (e.argument !== argument) {
                e = e.patch({ argument });
            }
    }
    return e;
}, true);
exports.default = simplify;
