#include "generator.hpp"

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

#include "literals.hpp"

namespace
{
    void opPushInt (Compiler::Generator::CodeContainer& code, int value)
    {
        code.push_back (Compiler::Generator::segment0 (0, value));
    }

    void opFetchIntLiteral (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (4));
    }

    void opFetchFloatLiteral (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (5));
    }

    void opIntToFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (3));
    }

    void opFloatToInt (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (6));
    }

    void opStoreLocalShort (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (0));
    }

    void opStoreLocalLong (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (1));
    }

    void opStoreLocalFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (2));
    }

    void opNegateInt (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (7));
    }

    void opNegateFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (8));
    }

    void opAddInt (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (9));
    }

    void opAddFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (10));
    }

    void opSubInt (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (11));
    }

    void opSubFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (12));
    }

    void opMulInt (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (13));
    }

    void opMulFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (14));
    }

    void opDivInt (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (15));
    }

    void opDivFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (16));
    }

    void opIntToFloat1 (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (17));
    }

    void opSquareRoot (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (19));
    }

    void opReturn (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (20));
    }

    void opMessageBox (Compiler::Generator::CodeContainer& code, int buttons)
    {
        code.push_back (Compiler::Generator::segment3 (0, buttons));
    }

    void opReport (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (58));
    }

    void opFetchLocalShort (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (21));
    }

    void opFetchLocalLong (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (22));
    }

    void opFetchLocalFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (23));
    }

    void opJumpForward (Compiler::Generator::CodeContainer& code, int offset)
    {
        code.push_back (Compiler::Generator::segment0 (1, offset));
    }

    void opJumpBackward (Compiler::Generator::CodeContainer& code, int offset)
    {
        code.push_back (Compiler::Generator::segment0 (2, offset));
    }

    /*
    Currently unused
    void opSkipOnZero (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (24));
    }
    */

    void opSkipOnNonZero (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (25));
    }

    void opEqualInt (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (26));
    }

    void opNonEqualInt (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (27));
    }

    void opLessThanInt (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (28));
    }

    void opLessOrEqualInt (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (29));
    }

    void opGreaterThanInt (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (30));
    }

    void opGreaterOrEqualInt (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (31));
    }

    void opEqualFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (32));
    }

    void opNonEqualFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (33));
    }

    void opLessThanFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (34));
    }

    void opLessOrEqualFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (35));
    }

    void opGreaterThanFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (36));
    }

    void opGreaterOrEqualFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (37));
    }

    void opMenuMode (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (38));
    }

    void opStoreGlobalShort (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (39));
    }

    void opStoreGlobalLong (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (40));
    }

    void opStoreGlobalFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (41));
    }

    void opFetchGlobalShort (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (42));
    }

    void opFetchGlobalLong (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (43));
    }

    void opFetchGlobalFloat (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (44));
    }

    void opStoreMemberShort (Compiler::Generator::CodeContainer& code, bool global)
    {
        code.push_back (Compiler::Generator::segment5 (global ? 65 : 59));
    }

    void opStoreMemberLong (Compiler::Generator::CodeContainer& code, bool global)
    {
        code.push_back (Compiler::Generator::segment5 (global ? 66 : 60));
    }

    void opStoreMemberFloat (Compiler::Generator::CodeContainer& code, bool global)
    {
        code.push_back (Compiler::Generator::segment5 (global ? 67 : 61));
    }

    void opFetchMemberShort (Compiler::Generator::CodeContainer& code, bool global)
    {
        code.push_back (Compiler::Generator::segment5 (global ? 68 : 62));
    }

    void opFetchMemberLong (Compiler::Generator::CodeContainer& code, bool global)
    {
        code.push_back (Compiler::Generator::segment5 (global ? 69 : 63));
    }

    void opFetchMemberFloat (Compiler::Generator::CodeContainer& code, bool global)
    {
        code.push_back (Compiler::Generator::segment5 (global ? 70 : 64));
    }

    void opRandom (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (45));
    }

    void opScriptRunning (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (46));
    }

    void opStartScript (Compiler::Generator::CodeContainer& code, bool targeted)
    {
        code.push_back (Compiler::Generator::segment5 (targeted ? 71 : 47));
    }

    void opStopScript (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (48));
    }

    void opGetDistance (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (49));
    }

    void opGetSecondsPassed (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (50));
    }

    void opEnable (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (51));
    }

    void opDisable (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (52));
    }

    void opGetDisabled (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (53));
    }

    void opEnableExplicit (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (54));
    }

    void opDisableExplicit (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (55));
    }

    void opGetDisabledExplicit (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (56));
    }

    void opGetDistanceExplicit (Compiler::Generator::CodeContainer& code)
    {
        code.push_back (Compiler::Generator::segment5 (57));
    }
}

namespace Compiler
{
    namespace Generator
    {
        void pushInt (CodeContainer& code, Literals& literals, int value)
        {
            int index = literals.addInteger (value);
            opPushInt (code, index);
            opFetchIntLiteral (code);
        }

        void pushFloat (CodeContainer& code, Literals& literals, float value)
        {
            int index = literals.addFloat (value);
            opPushInt (code, index);
            opFetchFloatLiteral (code);
        }

        void pushString (CodeContainer& code, Literals& literals, const std::string& value)
        {
            int index = literals.addString (value);
            opPushInt (code, index);
        }

        void assignToLocal (CodeContainer& code, char localType,
            int localIndex, const CodeContainer& value, char valueType)
        {
            opPushInt (code, localIndex);

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

            if (localType!=valueType)
            {
                if (localType=='f' && valueType=='l')
                {
                    opIntToFloat (code);
                }
                else if ((localType=='l' || localType=='s') && valueType=='f')
                {
                    opFloatToInt (code);
                }
            }

            switch (localType)
            {
                case 'f':

                    opStoreLocalFloat (code);
                    break;

                case 's':

                    opStoreLocalShort (code);
                    break;

                case 'l':

                    opStoreLocalLong (code);
                    break;

                default:

                    assert (0);
            }
        }

        void negate (CodeContainer& code, char valueType)
        {
            switch (valueType)
            {
                case 'l':

                    opNegateInt (code);
                    break;

                case 'f':

                    opNegateFloat (code);
                    break;

                default:

                    assert (0);
            }
        }

        void add (CodeContainer& code, char valueType1, char valueType2)
        {
            if (valueType1=='l' && valueType2=='l')
            {
                opAddInt (code);
            }
            else
            {
                if (valueType1=='l')
                    opIntToFloat1 (code);

                if (valueType2=='l')
                    opIntToFloat (code);

                opAddFloat (code);
            }
        }

        void sub (CodeContainer& code, char valueType1, char valueType2)
        {
            if (valueType1=='l' && valueType2=='l')
            {
                opSubInt (code);
            }
            else
            {
                if (valueType1=='l')
                    opIntToFloat1 (code);

                if (valueType2=='l')
                    opIntToFloat (code);

                opSubFloat (code);
            }
        }

        void mul (CodeContainer& code, char valueType1, char valueType2)
        {
            if (valueType1=='l' && valueType2=='l')
            {
                opMulInt (code);
            }
            else
            {
                if (valueType1=='l')
                    opIntToFloat1 (code);

                if (valueType2=='l')
                    opIntToFloat (code);

                opMulFloat (code);
            }
        }

        void div (CodeContainer& code, char valueType1, char valueType2)
        {
            if (valueType1=='l' && valueType2=='l')
            {
                opDivInt (code);
            }
            else
            {
                if (valueType1=='l')
                    opIntToFloat1 (code);

                if (valueType2=='l')
                    opIntToFloat (code);

                opDivFloat (code);
            }
        }

        void convert (CodeContainer& code, char fromType, char toType)
        {
            if (fromType!=toType)
            {
                if (fromType=='f' && toType=='l')
                    opFloatToInt (code);
                else if (fromType=='l' && toType=='f')
                    opIntToFloat (code);
                else
                    throw std::logic_error ("illegal type conversion");
            }
        }

        void squareRoot (CodeContainer& code)
        {
            opSquareRoot (code);
        }

        void exit (CodeContainer& code)
        {
            opReturn (code);
        }

        void message (CodeContainer& code, Literals& literals, const std::string& message,
            int buttons)
        {
            assert (buttons>=0);

            if (buttons>=256)
                throw std::runtime_error ("A message box can't have more than 255 buttons");

            int index = literals.addString (message);

            opPushInt (code, index);
            opMessageBox (code, buttons);
        }

        void report (CodeContainer& code, Literals& literals, const std::string& message)
        {
            int index = literals.addString (message);

            opPushInt (code, index);
            opReport (code);
        }

        void fetchLocal (CodeContainer& code, char localType, int localIndex)
        {
            opPushInt (code, localIndex);

            switch (localType)
            {
                case 'f':

                    opFetchLocalFloat (code);
                    break;

                case 's':

                    opFetchLocalShort (code);
                    break;

                case 'l':

                    opFetchLocalLong (code);
                    break;

                default:

                    assert (0);
            }
        }

        void jump (CodeContainer& code, int offset)
        {
            if (offset>0)
                opJumpForward (code, offset);
            else if (offset<0)
                opJumpBackward (code, -offset);
            else
                throw std::logic_error ("infinite loop");
        }

        void jumpOnZero (CodeContainer& code, int offset)
        {
            opSkipOnNonZero (code);

            if (offset<0)
                --offset; // compensate for skip instruction

            jump (code, offset);
        }

        void compare (CodeContainer& code, char op, char valueType1, char valueType2)
        {
            if (valueType1=='l' && valueType2=='l')
            {
                switch (op)
                {
                    case 'e': opEqualInt (code); break;
                    case 'n': opNonEqualInt (code); break;
                    case 'l': opLessThanInt (code); break;
                    case 'L': opLessOrEqualInt (code); break;
                    case 'g': opGreaterThanInt (code); break;
                    case 'G': opGreaterOrEqualInt (code); break;

                    default:

                        assert (0);
                }
            }
            else
            {
                if (valueType1=='l')
                    opIntToFloat1 (code);

                if (valueType2=='l')
                    opIntToFloat (code);

                switch (op)
                {
                    case 'e': opEqualFloat (code); break;
                    case 'n': opNonEqualFloat (code); break;
                    case 'l': opLessThanFloat (code); break;
                    case 'L': opLessOrEqualFloat (code); break;
                    case 'g': opGreaterThanFloat (code); break;
                    case 'G': opGreaterOrEqualFloat (code); break;

                    default:

                        assert (0);
                }
            }
        }

        void menuMode (CodeContainer& code)
        {
            opMenuMode (code);
        }

        void assignToGlobal (CodeContainer& code, Literals& literals, char localType,
            const std::string& name, const CodeContainer& value, char valueType)
        {
            int index = literals.addString (name);

            opPushInt (code, index);

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

            if (localType!=valueType)
            {
                if (localType=='f' && (valueType=='l' || valueType=='s'))
                {
                    opIntToFloat (code);
                }
                else if ((localType=='l' || localType=='s') && valueType=='f')
                {
                    opFloatToInt (code);
                }
            }

            switch (localType)
            {
                case 'f':

                    opStoreGlobalFloat (code);
                    break;

                case 's':

                    opStoreGlobalShort (code);
                    break;

                case 'l':

                    opStoreGlobalLong (code);
                    break;

                default:

                    assert (0);
            }
        }

        void fetchGlobal (CodeContainer& code, Literals& literals, char localType,
            const std::string& name)
        {
            int index = literals.addString (name);

            opPushInt (code, index);

            switch (localType)
            {
                case 'f':

                    opFetchGlobalFloat (code);
                    break;

                case 's':

                    opFetchGlobalShort (code);
                    break;

                case 'l':

                    opFetchGlobalLong (code);
                    break;

                default:

                    assert (0);
            }
        }

        void assignToMember (CodeContainer& code, Literals& literals, char localType,
            const std::string& name, const std::string& id, const CodeContainer& value,
            char valueType, bool global)
        {
            int index = literals.addString (name);

            opPushInt (code, index);

            index = literals.addString (id);

            opPushInt (code, index);

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

            if (localType!=valueType)
            {
                if (localType=='f' && (valueType=='l' || valueType=='s'))
                {
                    opIntToFloat (code);
                }
                else if ((localType=='l' || localType=='s') && valueType=='f')
                {
                    opFloatToInt (code);
                }
            }

            switch (localType)
            {
                case 'f':

                    opStoreMemberFloat (code, global);
                    break;

                case 's':

                    opStoreMemberShort (code, global);
                    break;

                case 'l':

                    opStoreMemberLong (code, global);
                    break;

                default:

                    assert (0);
            }
        }

        void fetchMember (CodeContainer& code, Literals& literals, char localType,
            const std::string& name, const std::string& id, bool global)
        {
            int index = literals.addString (name);

            opPushInt (code, index);

            index = literals.addString (id);

            opPushInt (code, index);

            switch (localType)
            {
                case 'f':

                    opFetchMemberFloat (code, global);
                    break;

                case 's':

                    opFetchMemberShort (code, global);
                    break;

                case 'l':

                    opFetchMemberLong (code, global);
                    break;

                default:

                    assert (0);
            }
        }

        void random (CodeContainer& code)
        {
            opRandom (code);
        }

        void scriptRunning (CodeContainer& code)
        {
            opScriptRunning (code);
        }

        void startScript (CodeContainer& code, Literals& literals, const std::string& id)
        {
            if (id.empty())
                opStartScript (code, false);
            else
            {
                int index = literals.addString (id);
                opPushInt (code, index);
                opStartScript (code, true);
            }
        }

        void stopScript (CodeContainer& code)
        {
            opStopScript (code);
        }

        void getDistance (CodeContainer& code, Literals& literals, const std::string& id)
        {
            if (id.empty())
            {
                opGetDistance (code);
            }
            else
            {
                int index = literals.addString (id);
                opPushInt (code, index);
                opGetDistanceExplicit (code);
            }
        }

        void getSecondsPassed (CodeContainer& code)
        {
            opGetSecondsPassed (code);
        }

        void getDisabled (CodeContainer& code, Literals& literals, const std::string& id)
        {
            if (id.empty())
            {
                opGetDisabled (code);
            }
            else
            {
                int index = literals.addString (id);
                opPushInt (code, index);
                opGetDisabledExplicit (code);
            }
        }

        void enable (CodeContainer& code, Literals& literals, const std::string& id)
        {
            if (id.empty())
            {
                opEnable (code);
            }
            else
            {
                int index = literals.addString (id);
                opPushInt (code, index);
                opEnableExplicit (code);
            }
        }

        void disable (CodeContainer& code, Literals& literals, const std::string& id)
        {
            if (id.empty())
            {
                opDisable (code);
            }
            else
            {
                int index = literals.addString (id);
                opPushInt (code, index);
                opDisableExplicit (code);
            }
        }
    }
}
