#include "exprparser.hpp"

#include <algorithm>
#include <cassert>
#include <iterator>
#include <sstream>
#include <stack>
#include <stdexcept>

#include <components/esm/refid.hpp>
#include <components/misc/strings/lower.hpp>

#include "context.hpp"
#include "discardparser.hpp"
#include "errorhandler.hpp"
#include "extensions.hpp"
#include "generator.hpp"
#include "junkparser.hpp"
#include "locals.hpp"
#include "scanner.hpp"
#include "stringparser.hpp"

namespace Compiler
{
    int ExprParser::getPriority(char op)
    {
        switch (op)
        {
            case '(':

                return 0;

            case 'e': // ==
            case 'n': // !=
            case 'l': // <
            case 'L': // <=
            case 'g': // <
            case 'G': // >=

                return 1;

            case '+':
            case '-':

                return 2;

            case '*':
            case '/':

                return 3;

            case 'm':

                return 4;
        }

        return 0;
    }

    char ExprParser::getOperandType(int index) const
    {
        assert(!mOperands.empty());
        assert(index >= 0);
        assert(index < static_cast<int>(mOperands.size()));
        return mOperands[mOperands.size() - 1 - index];
    }

    char ExprParser::getOperator() const
    {
        assert(!mOperators.empty());
        return mOperators[mOperators.size() - 1];
    }

    bool ExprParser::isOpen() const
    {
        return std::find(mOperators.begin(), mOperators.end(), '(') != mOperators.end();
    }

    void ExprParser::popOperator()
    {
        assert(!mOperators.empty());
        mOperators.resize(mOperators.size() - 1);
    }

    void ExprParser::popOperand()
    {
        assert(!mOperands.empty());
        mOperands.resize(mOperands.size() - 1);
    }

    void ExprParser::replaceBinaryOperands()
    {
        char t1 = getOperandType(1);
        char t2 = getOperandType();

        popOperand();
        popOperand();

        if (t1 == t2)
            mOperands.push_back(t1);
        else if (t1 == 'f' || t2 == 'f')
            mOperands.push_back('f');
        else
            throw std::logic_error("Failed to determine result operand type");
    }

    void ExprParser::pop()
    {
        char op = getOperator();

        switch (op)
        {
            case 'm':

                Generator::negate(mCode, getOperandType());
                popOperator();
                break;

            case '+':

                Generator::add(mCode, getOperandType(1), getOperandType());
                popOperator();
                replaceBinaryOperands();
                break;

            case '-':

                Generator::sub(mCode, getOperandType(1), getOperandType());
                popOperator();
                replaceBinaryOperands();
                break;

            case '*':

                Generator::mul(mCode, getOperandType(1), getOperandType());
                popOperator();
                replaceBinaryOperands();
                break;

            case '/':

                Generator::div(mCode, getOperandType(1), getOperandType());
                popOperator();
                replaceBinaryOperands();
                break;

            case 'e':
            case 'n':
            case 'l':
            case 'L':
            case 'g':
            case 'G':

                Generator::compare(mCode, op, getOperandType(1), getOperandType());
                popOperator();
                popOperand();
                popOperand();
                mOperands.push_back('l');
                break;

            default:

                throw std::logic_error("Unknown operator");
        }
    }

    void ExprParser::pushIntegerLiteral(int value)
    {
        mNextOperand = false;
        mOperands.push_back('l');
        Generator::pushInt(mCode, mLiterals, value);
    }

    void ExprParser::pushFloatLiteral(float value)
    {
        mNextOperand = false;
        mOperands.push_back('f');
        Generator::pushFloat(mCode, mLiterals, value);
    }

    void ExprParser::pushBinaryOperator(char c)
    {
        while (!mOperators.empty() && getPriority(getOperator()) >= getPriority(c))
            pop();

        mOperators.push_back(c);
        mNextOperand = true;
    }

    void ExprParser::close()
    {
        while (getOperator() != '(')
            pop();

        popOperator();
    }

    int ExprParser::parseArguments(const std::string& arguments, Scanner& scanner)
    {
        return parseArguments(arguments, scanner, mCode);
    }

    bool ExprParser::handleMemberAccess(const std::string& name)
    {
        mMemberOp = false;

        std::string name2 = Misc::StringUtils::lowerCase(name);
        auto id = ESM::RefId::stringRefId(Misc::StringUtils::lowerCase(mExplicit));

        std::pair<char, bool> type = getContext().getMemberType(name2, id);

        if (type.first != ' ')
        {
            Generator::fetchMember(mCode, mLiterals, type.first, name2, id.getRefIdString(), !type.second);

            mNextOperand = false;
            mExplicit.clear();
            mOperands.push_back(type.first == 'f' ? 'f' : 'l');
            return true;
        }

        return false;
    }

    ExprParser::ExprParser(
        ErrorHandler& errorHandler, const Context& context, Locals& locals, Literals& literals, bool argument)
        : Parser(errorHandler, context)
        , mLocals(locals)
        , mLiterals(literals)
        , mNextOperand(true)
        , mFirst(true)
        , mArgument(argument)
        , mRefOp(false)
        , mMemberOp(false)
    {
    }

    bool ExprParser::parseInt(int value, const TokenLoc& loc, Scanner& scanner)
    {
        if (!mExplicit.empty())
            return Parser::parseInt(value, loc, scanner);

        mFirst = false;

        if (mNextOperand)
        {
            start();

            pushIntegerLiteral(value);
            mTokenLoc = loc;
            return true;
        }
        else
        {
            scanner.putbackInt(value, loc);
            return false;
        }
    }

    bool ExprParser::parseFloat(float value, const TokenLoc& loc, Scanner& scanner)
    {
        if (!mExplicit.empty())
            return Parser::parseFloat(value, loc, scanner);

        mFirst = false;

        if (mNextOperand)
        {
            start();

            pushFloatLiteral(value);
            mTokenLoc = loc;
            return true;
        }
        else
        {
            scanner.putbackFloat(value, loc);
            return false;
        }
    }

    bool ExprParser::parseName(const std::string& name, const TokenLoc& loc, Scanner& scanner)
    {
        if (!mExplicit.empty())
        {
            if (!mRefOp)
            {
                if (mMemberOp && handleMemberAccess(name))
                    return true;

                return Parser::parseName(name, loc, scanner);
            }
            else
            {
                mExplicit.clear();
                getErrorHandler().warning("Stray explicit reference", loc);
            }
        }

        mFirst = false;

        if (mNextOperand)
        {
            start();

            std::string name2 = Misc::StringUtils::lowerCase(name);

            char type = mLocals.getType(name2);

            if (type != ' ')
            {
                Generator::fetchLocal(mCode, type, mLocals.getIndex(name2));
                mNextOperand = false;
                mOperands.push_back(type == 'f' ? 'f' : 'l');
                return true;
            }

            type = getContext().getGlobalType(name2);

            if (type != ' ')
            {
                Generator::fetchGlobal(mCode, mLiterals, type, name2);
                mNextOperand = false;
                mOperands.push_back(type == 'f' ? 'f' : 'l');
                return true;
            }

            if (mExplicit.empty() && getContext().isId(ESM::RefId::stringRefId(name2)))
            {
                mExplicit = std::move(name2);
                return true;
            }

            // This is terrible, but of course we must have this for legacy content.
            // Convert the string to a number even if it's impossible and use it as a number literal.
            // Can't use stof/atof or to_string out of locale concerns.
            float number;
            std::stringstream stream(name2);
            stream >> number;
            stream.str(std::string());
            stream.clear();
            stream << number;

            pushFloatLiteral(number);
            mTokenLoc = loc;
            getErrorHandler().warning("Parsing a non-variable string as a number: " + stream.str(), loc);
            return true;
        }
        else
        {
            scanner.putbackName(name, loc);
            return false;
        }
    }

    bool ExprParser::parseKeyword(int keyword, const TokenLoc& loc, Scanner& scanner)
    {
        if (const Extensions* extensions = getContext().getExtensions())
        {
            char returnType; // ignored
            std::string argumentType; // ignored
            bool hasExplicit = false; // ignored
            bool isInstruction = extensions->isInstruction(keyword, argumentType, hasExplicit);

            if (isInstruction
                || (mExplicit.empty() && extensions->isFunction(keyword, returnType, argumentType, hasExplicit)))
            {
                std::string name = loc.mLiteral;
                if (name.size() >= 2 && name[0] == '"' && name[name.size() - 1] == '"')
                    name = name.substr(1, name.size() - 2);
                if (isInstruction || mLocals.getType(Misc::StringUtils::lowerCase(name)) != ' ')
                {
                    // pretend this is not a keyword
                    return parseName(name, loc, scanner);
                }
            }
        }

        if (keyword == Scanner::K_end || keyword == Scanner::K_begin || keyword == Scanner::K_short
            || keyword == Scanner::K_long || keyword == Scanner::K_float || keyword == Scanner::K_if
            || keyword == Scanner::K_endif || keyword == Scanner::K_else || keyword == Scanner::K_elseif
            || keyword == Scanner::K_while || keyword == Scanner::K_endwhile || keyword == Scanner::K_return
            || keyword == Scanner::K_messagebox || keyword == Scanner::K_set || keyword == Scanner::K_to)
        {
            return parseName(loc.mLiteral, loc, scanner);
        }

        mFirst = false;

        if (!mExplicit.empty())
        {
            if (mRefOp && mNextOperand)
            {

                // check for custom extensions
                if (const Extensions* extensions = getContext().getExtensions())
                {
                    char returnType;
                    std::string argumentType;

                    bool hasExplicit = true;
                    if (extensions->isFunction(keyword, returnType, argumentType, hasExplicit))
                    {
                        if (!hasExplicit)
                        {
                            getErrorHandler().warning("Stray explicit reference", loc);
                            mExplicit.clear();
                        }

                        start();

                        mTokenLoc = loc;
                        int optionals = parseArguments(argumentType, scanner);

                        extensions->generateFunctionCode(keyword, mCode, mLiterals, mExplicit, optionals);
                        mOperands.push_back(returnType);
                        mExplicit.clear();
                        mRefOp = false;

                        mNextOperand = false;
                        return true;
                    }
                }
            }

            return Parser::parseKeyword(keyword, loc, scanner);
        }

        if (mNextOperand)
        {
            // check for custom extensions
            if (const Extensions* extensions = getContext().getExtensions())
            {
                start();

                char returnType;
                std::string argumentType;

                bool hasExplicit = false;

                if (extensions->isFunction(keyword, returnType, argumentType, hasExplicit))
                {
                    mTokenLoc = loc;
                    int optionals = parseArguments(argumentType, scanner);

                    extensions->generateFunctionCode(keyword, mCode, mLiterals, "", optionals);
                    mOperands.push_back(returnType);

                    mNextOperand = false;
                    return true;
                }
            }
        }
        else
        {
            scanner.putbackKeyword(keyword, loc);
            return false;
        }

        return Parser::parseKeyword(keyword, loc, scanner);
    }

    bool ExprParser::parseSpecial(int code, const TokenLoc& loc, Scanner& scanner)
    {
        if (!mExplicit.empty())
        {
            if (mRefOp && code == Scanner::S_open)
            {
                /// \todo add option to disable this workaround
                mOperators.push_back('(');
                mTokenLoc = loc;
                return true;
            }

            if (!mRefOp && code == Scanner::S_ref)
            {
                mRefOp = true;
                return true;
            }

            if (!mMemberOp && code == Scanner::S_member)
            {
                mMemberOp = true;
                return true;
            }

            return Parser::parseSpecial(code, loc, scanner);
        }

        mFirst = false;

        if (code == Scanner::S_newline)
        {
            // end marker
            if (mTokenLoc.mLiteral.empty())
                mTokenLoc = loc;
            scanner.putbackSpecial(code, loc);
            return false;
        }

        if (code == Scanner::S_minus && mNextOperand)
        {
            // unary
            mOperators.push_back('m');
            mTokenLoc = loc;
            return true;
        }

        if (code == Scanner::S_plus && mNextOperand)
        {
            // Also unary, but +, just ignore it
            mTokenLoc = loc;
            return true;
        }

        if (code == Scanner::S_open)
        {
            if (mNextOperand)
            {
                mOperators.push_back('(');
                mTokenLoc = loc;
                return true;
            }
            else
            {
                scanner.putbackSpecial(code, loc);
                return false;
            }
        }

        if (code == Scanner::S_close && !mNextOperand)
        {
            if (isOpen())
            {
                close();
                return true;
            }

            mTokenLoc = loc;
            scanner.putbackSpecial(code, loc);
            return false;
        }

        if (!mNextOperand)
        {
            mTokenLoc = loc;
            char c = 0; // comparison

            switch (code)
            {
                case Scanner::S_plus:
                    c = '+';
                    break;
                case Scanner::S_minus:
                    c = '-';
                    break;
                case Scanner::S_mult:
                    pushBinaryOperator('*');
                    return true;
                case Scanner::S_div:
                    pushBinaryOperator('/');
                    return true;
                case Scanner::S_cmpEQ:
                    c = 'e';
                    break;
                case Scanner::S_cmpNE:
                    c = 'n';
                    break;
                case Scanner::S_cmpLT:
                    c = 'l';
                    break;
                case Scanner::S_cmpLE:
                    c = 'L';
                    break;
                case Scanner::S_cmpGT:
                    c = 'g';
                    break;
                case Scanner::S_cmpGE:
                    c = 'G';
                    break;
            }

            if (c)
            {
                if (mArgument && !isOpen())
                {
                    // expression ends here
                    // Thank you Morrowind for this rotten syntax :(
                    scanner.putbackSpecial(code, loc);
                    return false;
                }

                pushBinaryOperator(c);
                return true;
            }
        }

        return Parser::parseSpecial(code, loc, scanner);
    }

    void ExprParser::reset()
    {
        mOperands.clear();
        mOperators.clear();
        mNextOperand = true;
        mCode.clear();
        mFirst = true;
        mExplicit.clear();
        mRefOp = false;
        mMemberOp = false;
        Parser::reset();
    }

    char ExprParser::append(std::vector<Interpreter::Type_Code>& code)
    {
        if (mOperands.empty() && mOperators.empty())
        {
            getErrorHandler().error("Missing expression", mTokenLoc);
            return 'l';
        }

        if (mNextOperand || mOperands.empty())
        {
            getErrorHandler().error("Syntax error in expression", mTokenLoc);
            return 'l';
        }

        while (!mOperators.empty())
            pop();

        std::copy(mCode.begin(), mCode.end(), std::back_inserter(code));

        assert(mOperands.size() == 1);
        return mOperands[0];
    }

    int ExprParser::parseArguments(const std::string& arguments, Scanner& scanner,
        std::vector<Interpreter::Type_Code>& code, int ignoreKeyword, bool expectNames)
    {
        bool optional = false;
        int optionalCount = 0;

        ExprParser parser(getErrorHandler(), getContext(), mLocals, mLiterals, true);
        StringParser stringParser(getErrorHandler(), getContext(), mLiterals);
        DiscardParser discardParser(getErrorHandler(), getContext());
        JunkParser junkParser(getErrorHandler(), getContext(), ignoreKeyword);

        std::stack<std::vector<Interpreter::Type_Code>> stack;

        for (char argument : arguments)
        {
            if (argument == '/')
            {
                optional = true;
            }
            else if (argument == 'S' || argument == 'c' || argument == 'x')
            {
                stringParser.reset();

                if (optional || argument == 'x')
                    stringParser.setOptional(true);

                if (argument == 'c')
                    stringParser.smashCase();
                if (argument == 'x')
                    stringParser.discard();
                scanner.enableExpectName();
                scanner.scan(stringParser);

                if ((optional || argument == 'x') && stringParser.isEmpty())
                    break;

                if (argument != 'x')
                {
                    std::vector<Interpreter::Type_Code> tmp;
                    stringParser.append(tmp);

                    stack.push(tmp);

                    if (optional)
                        ++optionalCount;
                }
                else
                    getErrorHandler().warning("Extra argument", stringParser.getTokenLoc());
            }
            else if (argument == 'X')
            {
                parser.reset();

                parser.setOptional(true);

                scanner.scan(parser);

                if (parser.isEmpty())
                    break;
                else
                    getErrorHandler().warning("Extra argument", parser.getTokenLoc());
            }
            else if (argument == 'z')
            {
                discardParser.reset();
                discardParser.setOptional(true);

                scanner.scan(discardParser);

                if (discardParser.isEmpty())
                    break;
                else
                    getErrorHandler().warning("Extra argument", discardParser.getTokenLoc());
            }
            else if (argument == 'j')
            {
                /// \todo disable this when operating in strict mode
                junkParser.reset();

                scanner.scan(junkParser);
            }
            else
            {
                parser.reset();

                if (optional)
                    parser.setOptional(true);
                if (expectNames)
                    scanner.enableExpectName();

                scanner.scan(parser);

                if (optional && parser.isEmpty())
                    break;

                std::vector<Interpreter::Type_Code> tmp;

                char type = parser.append(tmp);

                if (type != argument)
                    Generator::convert(tmp, type, argument);

                stack.push(tmp);

                if (optional)
                    ++optionalCount;
            }
        }

        while (!stack.empty())
        {
            std::vector<Interpreter::Type_Code>& tmp = stack.top();

            std::copy(tmp.begin(), tmp.end(), std::back_inserter(code));

            stack.pop();
        }

        return optionalCount;
    }

    const TokenLoc& ExprParser::getTokenLoc() const
    {
        return mTokenLoc;
    }
}
