/**

GDevelop - Timed Event Extension
Copyright (c) 2011-2016 Florian Rival (Florian.Rival@gmail.com)
This project is released under the MIT License.
*/

#include "GDCpp/Extensions/ExtensionBase.h"
#include "GDCore/Tools/Version.h"

#include "TimedEvent.h"
#include "TimedEventsManager.h"
#include "GDCpp/Runtime/CommonTools.h"
#include "GDCore/Events/CodeGeneration/EventsCodeGenerator.h"
#include "GDCore/Events/CodeGeneration/ExpressionsCodeGeneration.h"
#include "GDCore/Events/CodeGeneration/EventsCodeGenerationContext.h"
#include "GDCore/Events/Tools/EventsCodeNameMangler.h"
#include "GDCpp/Runtime/RuntimeScene.h"
#include "GDCore/Events/CodeGeneration/EventsCodeGenerationContext.h"

/**
 * \brief This class declares information about the extension.
 */
class Extension : public ExtensionBase
{
public:

    /**
     * Constructor of an extension declares everything the extension contains: objects, actions, conditions and expressions.
     */
    Extension()
    {
        SetExtensionInformation("TimedEvent",
                              _("Timed events"),
                              _("Event which launch its conditions and actions only after a amount of time is reached."),
                              "Florian Rival",
                              "Open source (MIT License)");

        #if defined(GD_IDE_ONLY)


        AddEvent("TimedEvent",
                      _("Timed event"),
                      _("Event which launch its conditions and actions only after a amount of time is reached."),
                      "",
                      "CppPlatform/Extensions/timedevent16.png",
                      std::shared_ptr<gd::BaseEvent>(new TimedEvent))
            .SetCodeGenerator([](gd::BaseEvent & event_, gd::EventsCodeGenerator & codeGenerator, gd::EventsCodeGenerationContext & context) {
                TimedEvent & event = dynamic_cast<TimedEvent&>(event_);

                codeGenerator.AddIncludeFile("TimedEvent/TimedEventTools.h");

                //Notify parent timed event that they have a child
                for (std::size_t i = 0;i<TimedEvent::codeGenerationCurrentParents.size();++i)
                    TimedEvent::codeGenerationCurrentParents[i]->codeGenerationChildren.push_back(&event);

                //And register this event as potential parent
                TimedEvent::codeGenerationCurrentParents.push_back(&event);
                event.codeGenerationChildren.clear();

                //Prepare code for computing timeout
                gd::String timeOutCode;
                gd::CallbacksForGeneratingExpressionCode callbacks(timeOutCode, codeGenerator, context);
                gd::ExpressionParser parser(event.GetTimeoutExpression());
                if (!parser.ParseMathExpression(codeGenerator.GetPlatform(), codeGenerator.GetProject(), codeGenerator.GetLayout(), callbacks) || timeOutCode.empty()) timeOutCode = "0";

                //Prepare name
                gd::String codeName = !event.GetName().empty() ? "GDNamedTimedEvent_"+codeGenerator.ConvertToString(event.GetName()) : "GDTimedEvent_"+gd::String::From(&event);

                gd::String outputCode;

                outputCode += "if ( static_cast<double>(GDpriv::TimedEvents::UpdateAndGetTimeOf(*runtimeContext->scene, \""+codeName+"\"))/1000000.0 > "+timeOutCode+")";
                outputCode += "{";

                outputCode += codeGenerator.GenerateConditionsListCode(event.GetConditions(), context);

                gd::String ifPredicat;
                for (std::size_t i = 0;i<event.GetConditions().size();++i)
                {
                    if (i!=0) ifPredicat += " && ";
                    ifPredicat += "condition"+gd::String::From(i)+"IsTrue";
                }

                if ( !ifPredicat.empty() ) outputCode += "if (" +ifPredicat+ ")\n";
                outputCode += "{\n";
                outputCode += codeGenerator.GenerateActionsListCode(event.GetActions(), context);
                if ( event.HasSubEvents() ) //Sub events
                {
                    outputCode += "\n{\n";
                    outputCode += codeGenerator.GenerateEventsListCode(event.GetSubEvents(), context);
                    outputCode += "}\n";
                }

                outputCode += "}\n";

                outputCode += "}";

                //This event cannot be a parent of other TimedEvent anymore
                if (!TimedEvent::codeGenerationCurrentParents.empty())
                    TimedEvent::codeGenerationCurrentParents.pop_back();
                else
                    std::cout << "Error! CodeGenerationCurrentParents cannot be empty!";

                return outputCode;
            });

        AddAction("ResetTimedEvent",
                       _("Reset a timed event"),
                       _("Reset a timed event"),
                       _("Reset the timed event(s) called _PARAM1_"),
                       _("Timed events"),
                       "CppPlatform/Extensions/timedevent24.png",
                       "CppPlatform/Extensions/timedevent16.png")
            .AddCodeOnlyParameter("currentScene", "")
            .AddParameter("", _("Name"))
            .codeExtraInformation.SetCustomCodeGenerator([](gd::Instruction & instruction, gd::EventsCodeGenerator & codeGenerator, gd::EventsCodeGenerationContext & context) {
                codeGenerator.AddIncludeFile("TimedEvent/TimedEventTools.h");

                gd::String codeName = "GDNamedTimedEvent_"+codeGenerator.ConvertToString(instruction.GetParameter(1).GetPlainString());
                return "GDpriv::TimedEvents::Reset(*runtimeContext->scene, \""+codeName+"\");\n";

                return gd::String("");
            });

        AddAction("ResetTimedEventAndSubs",
                       _("Reset a timed event and sub events"),
                       _("Reset a timed events, as well as all of its sub events."),
                       _("Reset timed events called _PARAM1_ and their sub events"),
                       _("Timed events"),
                       "CppPlatform/Extensions/timedevent24.png",
                       "CppPlatform/Extensions/timedevent16.png")
            .AddParameter("", _("Name"))
            .codeExtraInformation.SetCustomCodeGenerator([](gd::Instruction & instruction, gd::EventsCodeGenerator & codeGenerator, gd::EventsCodeGenerationContext & context) {
                codeGenerator.AddIncludeFile("TimedEvent/TimedEventTools.h");

                for (std::size_t i = 0;i<TimedEvent::codeGenerationCurrentParents.size();++i)
                {
                    if ( TimedEvent::codeGenerationCurrentParents[i] == NULL )
                    {
                        std::cout << "WARNING : NULL timed event in codeGenerationCurrentParents";
                        continue;
                    }

                    if (TimedEvent::codeGenerationCurrentParents[i]->GetName() == instruction.GetParameter(0).GetPlainString())
                    {
                        TimedEvent & timedEvent = *TimedEvent::codeGenerationCurrentParents[i];

                        gd::String code;
                        {
                            gd::String codeName = !timedEvent.GetName().empty() ? "GDNamedTimedEvent_"+codeGenerator.ConvertToString(timedEvent.GetName()) : "GDTimedEvent_"+gd::String::From(&timedEvent);
                            code += "GDpriv::TimedEvents::Reset(*runtimeContext->scene, \""+codeName+"\");\n";
                        }
                        for (std::size_t j = 0;j<timedEvent.codeGenerationChildren.size();++j)
                        {
                            gd::String codeName = !timedEvent.codeGenerationChildren[j]->GetName().empty() ? "GDNamedTimedEvent_"+codeGenerator.ConvertToString(timedEvent.codeGenerationChildren[j]->GetName()) : "GDTimedEvent_"+gd::String::From(timedEvent.codeGenerationChildren[j]);
                            code += "GDpriv::TimedEvents::Reset(*runtimeContext->scene, \""+codeName+"\");\n";
                        }
                        return code;
                    }
                }

                return gd::String("");
            })
            .SetIncludeFile("TimedEvent/TimedEventTools.h");
        #endif

        GD_COMPLETE_EXTENSION_COMPILATION_INFORMATION();
    };

    #if defined(GD_IDE_ONLY)
    bool HasDebuggingProperties() const { return true; };

    void GetPropertyForDebugger(RuntimeScene & scene, std::size_t propertyNb, gd::String & name, gd::String & value) const
    {
        std::size_t i = 0;
        std::map < gd::String, ManualTimer >::const_iterator end = TimedEventsManager::managers[&scene].timedEvents.end();
        for (std::map < gd::String, ManualTimer >::iterator iter = TimedEventsManager::managers[&scene].timedEvents.begin();iter != end;++iter)
        {
            if ( propertyNb == i )
            {
                name = iter->first;
                //Unmangle name
                if ( name.find("GDNamedTimedEvent_") == 0 && name.length() > 18 )
                    name = name.substr(18, name.length());
                else
                    name = _("No name");

                value = gd::String::From(static_cast<double>(iter->second.GetTime())/1000000.0)+"s";

                return;
            }

            ++i;
        }
    }

    bool ChangeProperty(RuntimeScene & scene, std::size_t propertyNb, gd::String newValue)
    {
        std::size_t i = 0;
        std::map < gd::String, ManualTimer >::const_iterator end = TimedEventsManager::managers[&scene].timedEvents.end();
        for (std::map < gd::String, ManualTimer >::iterator iter = TimedEventsManager::managers[&scene].timedEvents.begin();iter != end;++iter)
        {
            if ( propertyNb == i )
            {
                iter->second.SetTime(newValue.To<double>()*1000000.0);

                return true;
            }

            ++i;
        }

        return false;
    }

    std::size_t GetNumberOfProperties(RuntimeScene & scene) const
    {
        return TimedEventsManager::managers[&scene].timedEvents.size();
    }
    #endif

    void SceneLoaded(RuntimeScene & scene)
    {
        TimedEventsManager::managers[&scene].timedEvents.clear();
    }
};

/**
 * Used by GDevelop to create the extension class
 * -- Do not need to be modified. --
 */
extern "C" ExtensionBase * GD_EXTENSION_API CreateGDExtension() {
    return new Extension;
}
