#ifndef INTERPRETER_MISCOPCODES_H_INCLUDED
#define INTERPRETER_MISCOPCODES_H_INCLUDED

#include <cstdlib>
#include <stdexcept>
#include <vector>
#include <string>
#include <sstream>
#include <algorithm>

#include "opcodes.hpp"
#include "runtime.hpp"

namespace Interpreter
{
    class OpMessageBox : public Opcode1
    {
        public:

            virtual void execute (Runtime& runtime, unsigned int arg0)
            {
                // message
                int index = runtime[0].mInteger;
                runtime.pop();
                std::string message = runtime.getStringLiteral (index);

                // buttons
                std::vector<std::string> buttons;

                for (std::size_t i=0; i<arg0; ++i)
                {
                    int index = runtime[0].mInteger;
                    runtime.pop();
                    buttons.push_back (runtime.getStringLiteral (index));
                }

                std::reverse (buttons.begin(), buttons.end());

                // additional parameters
                std::string formattedMessage;

                for (std::size_t i=0; i<message.size(); ++i)
                {
                    char c = message[i];

                    if (c!='%')
                        formattedMessage += c;
                    else
                    {
                        ++i;
                        if (i<message.size())
                        {
                            c = message[i];

                            if (c=='S' || c=='s')
                            {
                                int index = runtime[0].mInteger;
                                runtime.pop();
                                formattedMessage += runtime.getStringLiteral (index);
                            }
                            else if (c=='g' || c=='G')
                            {
                                Type_Integer value = runtime[0].mInteger;
                                runtime.pop();

                                std::ostringstream out;
                                out << value;
                                formattedMessage += out.str();
                            }
                            else if (c=='f' || c=='F' || c=='.')
                            {
                                while (c!='f' && i<message.size())
                                {
                                    ++i;
                                }

                                float value = runtime[0].mFloat;
                                runtime.pop();

                                std::ostringstream out;
                                out << value;
                                formattedMessage += out.str();
                            }
                            else if (c=='%')
                                formattedMessage += "%";
                            else
                            {
                                formattedMessage += "%";
                                formattedMessage += c;
                            }
                        }
                    }
                }

                runtime.getContext().messageBox (formattedMessage, buttons);
            }
    };

    class OpMenuMode : public Opcode0
    {
        public:

            virtual void execute (Runtime& runtime)
            {
                runtime.push (runtime.getContext().menuMode());
            }
    };

    class OpRandom : public Opcode0
    {
        public:

            virtual void execute (Runtime& runtime)
            {
                double r = static_cast<double> (std::rand()) / RAND_MAX; // [0, 1)

                Type_Integer limit = runtime[0].mInteger;

                if (limit<0)
                    throw std::runtime_error (
                        "random: argument out of range (Don't be so negative!)");

                Type_Integer value = static_cast<Type_Integer> (r*limit); // [o, limit)

                runtime[0].mInteger = value;
            }
    };

    class OpGetSecondsPassed : public Opcode0
    {
        public:

            virtual void execute (Runtime& runtime)
            {
                Type_Float duration = runtime.getContext().getSecondsPassed();

                runtime.push (duration);
            }
    };

    class OpEnable : public Opcode0
    {
        public:

            virtual void execute (Runtime& runtime)
            {
                runtime.getContext().enable();
            }
    };

    class OpDisable : public Opcode0
    {
        public:

            virtual void execute (Runtime& runtime)
            {
                runtime.getContext().disable();
            }
    };

    class OpGetDisabled : public Opcode0
    {
        public:

            virtual void execute (Runtime& runtime)
            {
                runtime.push (runtime.getContext().isDisabled());
            }
    };

    class OpEnableExplicit : public Opcode0
    {
        public:

            virtual void execute (Runtime& runtime)
            {
                int index = runtime[0].mInteger;
                runtime.pop();
                std::string id = runtime.getStringLiteral (index);

                runtime.getContext().enable (id);
            }
    };

    class OpDisableExplicit : public Opcode0
    {
        public:

            virtual void execute (Runtime& runtime)
            {
                int index = runtime[0].mInteger;
                runtime.pop();
                std::string id = runtime.getStringLiteral (index);

                runtime.getContext().disable (id);
            }
    };

    class OpGetDisabledExplicit : public Opcode0
    {
        public:

            virtual void execute (Runtime& runtime)
            {
                int index = runtime[0].mInteger;
                runtime.pop();
                std::string id = runtime.getStringLiteral (index);

                runtime.push (runtime.getContext().isDisabled (id));
            }
    };

}

#endif