#ifndef _JFXMAP_LOGIC_EVALUATOR_H_
#define _JFXMAP_LOGIC_EVALUATOR_H_

#include <stack>
#include <string>

#include "Utils.h"

namespace jf {

class LogicEvaluator {
   public:
    enum class WordType { kvalue, kvariable, kand, kor, keq, klt, kgt, kle, kge, kleft_bracket, kright_bracket };
    std::map<WordType, int> priority{{WordType::kvalue, 0}, {WordType::kand, 1}, {WordType::kor, 1}, {WordType::keq, 2},           {WordType::klt, 2},
                                     {WordType::kgt, 2},    {WordType::kle, 2},  {WordType::kge, 2}, {WordType::kleft_bracket, 0}, {WordType::kright_bracket, 0}};
    struct Word {
        WordType t;
        std::string v;
    };
    LogicEvaluator(const std::string& in_infix) { Scan(in_infix); }
    bool IsValid(const std::map<std::string, std::string>& testParams) {
        if (infix.size() == 0) {
            return true;
        }

        std::stack<Word> postfix;
        std::stack<Word> ops;
        for (Word& w : infix) {
            if (w.t == WordType::kvalue || w.t == WordType::kvariable) {
                postfix.push(w);
            } else if (w.t == WordType::kleft_bracket) {
                ops.push(w);
            } else if (w.t == WordType::kright_bracket) {
                while (true) {
                    Word& op = ops.top();
                    if (op.t == WordType::kleft_bracket) {
                        ops.pop();
                        break;
                    }
                    ops.pop();
                    Eval(op, testParams, postfix);
                }
            } else {
                if (ops.empty()) {
                    ops.push(w);
                } else {
                    while (true) {
                        if (ops.empty()) {
                            ops.push(w);
                            break;
                        }
                        Word& op = ops.top();
                        if (priority[op.t] >= priority[w.t]) {
                            ops.pop();
                            Eval(op, testParams, postfix);
                        } else {
                            ops.push(w);
                            break;
                        }
                    }
                }
            }
        }
        while (ops.size() > 0) {
            Word op = ops.top();
            ops.pop();
            Eval(op, testParams, postfix);
        }
        assert(postfix.size() == 1);
        return from_string<double>(postfix.top().v) != 0;
    }

    void Eval(const Word& op, const std::map<std::string, std::string>& testParams, std::stack<Word>& postfix) {
        Word p2 = postfix.top();
        postfix.pop();
        Word p1 = postfix.top();
        postfix.pop();
        if (p1.t == WordType::kvariable) {
            p1.v = testParams.at(p1.v);
        }
        if (p2.t == WordType::kvariable) {
            p2.v = testParams.at(p2.v);
        }
        double v;
        switch (op.t) {
            case WordType::kand: {
                v = jf::from_string<double>(p1.v) && jf::from_string<double>(p2.v);
                break;
            };
            case WordType::kor: {
                v = jf::from_string<double>(p1.v) || jf::from_string<double>(p2.v);
                break;
            };
            case WordType::keq: {
                v = jf::from_string<double>(p1.v) == jf::from_string<double>(p2.v);
                break;
            }
            case WordType::klt: {
                v = jf::from_string<double>(p1.v) < jf::from_string<double>(p2.v);
                break;
            }
            case WordType::kle: {
                v = jf::from_string<double>(p1.v) <= jf::from_string<double>(p2.v);
                break;
            }
            case WordType::kgt: {
                v = jf::from_string<double>(p1.v) > jf::from_string<double>(p2.v);
                break;
            }
            case WordType::kge: {
                v = jf::from_string<double>(p1.v) >= jf::from_string<double>(p2.v);
                break;
            }
        }
        postfix.push(Word{WordType::kvalue, jf::to_string(v)});
    }

   private:
    void Scan(const std::string& in) {
        auto is_digit = [](char c) { return c <= '9' && c >= '0'; };
        auto is_cap = [](char c) { return (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a'); };
        std::string::const_iterator scanner = in.begin();
        while (scanner != in.end()) {
            if (*scanner == '&') {
                infix.push_back({WordType::kand, "&&"});
                scanner += 2;
            } else if (*scanner == '|') {
                infix.push_back({WordType::kor, "||"});
                scanner += 2;
            } else if (*scanner == ' ') {
                scanner++;
            } else if (is_digit(*scanner)) {
                std::string num;
                num.push_back(*scanner);
                scanner++;
                while (is_digit(*scanner)) {
                    num.push_back(*scanner);
                    scanner++;
                }
                infix.push_back({WordType::kvalue, num});
            } else if (is_cap(*scanner)) {
                std::string v;
                v.push_back(*scanner);
                scanner++;
                while (is_cap(*scanner)) {
                    v.push_back(*scanner);
                    scanner++;
                }
                infix.push_back({WordType::kvariable, v});
            } else if (*scanner == '(') {
                infix.push_back({WordType::kleft_bracket, "("});
                scanner++;
            } else if (*scanner == ')') {
                infix.push_back({WordType::kright_bracket, ")"});
                scanner++;
            } else if (*scanner == '<') {
                if (*(scanner + 1) == '=') {
                    infix.push_back({WordType::kle, "<="});
                    scanner += 2;
                } else {
                    infix.push_back({WordType::klt, "<"});
                    scanner++;
                }
            } else if (*scanner == '>') {
                if (*(scanner + 1) == '=') {
                    infix.push_back({WordType::kge, ">="});
                    scanner += 2;
                } else {
                    infix.push_back({WordType::kgt, ">"});
                    scanner++;
                }
            } else if (*scanner == '=') {
                infix.push_back({WordType::keq, "=="});
                scanner += 2;
            }
        }
        return;
    }

   private:
    std::vector<Word> infix;
};

}  // namespace jf

#endif