/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/

#include "StartingPointInput_precompiled.h"
#include <platform_impl.h>

#include "Input.h"
#include "InputNode.h"
#include "InputLibrary.h"
#include "LyToAzInputNameConversions.h"

#include <AzCore/Module/Module.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Math/VectorFloat.h>

#include <AzCore/Module/Environment.h>
#include <AzCore/Component/Component.h>

namespace ClassConverters
{

    static bool ConvertToInput(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement);
    static bool BaseClassDeprecator(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement);
    static bool ConvertHeldVersion(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement);
    static bool ConvertAnalogVersion(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement);
}

namespace StartingPointInput
{
    // Dummy component to get reflect function
    class StartingPointInputDummyComponent
        : public AZ::Component
    {
    public:
        AZ_COMPONENT(StartingPointInputDummyComponent, "{0F784CD5-C5AB-4673-8651-ABFA66060232}");

        static void Reflect(AZ::ReflectContext* context)
        {
            Input::InputLibrary::Reflect(context);
            Input::Input::Reflect(context);
            Input::ThumbstickInput::Reflect(context);

            AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
            if (serializeContext)
            {
                serializeContext->Class<StartingPointInputDummyComponent, AZ::Component>()
                    ->Version(0)
                    ;

                serializeContext->ClassDeprecate("SingleEventToAction", "{2C93824D-D011-459C-B12B-9F4A6148730C}", &ClassConverters::BaseClassDeprecator);
                serializeContext->ClassDeprecate("Held", "{A3F1D51B-3473-49D6-9131-98D1FBA9003A}", &ClassConverters::ConvertHeldVersion);
                serializeContext->ClassDeprecate("Analog", "{806F21D9-11EA-47FC-8B89-FDB67AADE4FF}", &ClassConverters::ConvertAnalogVersion);
                serializeContext->ClassDeprecate("Pressed", "{8C6868E9-CBFF-4B42-AC72-FD22421F9C6A}", &ClassConverters::ConvertToInput);
                serializeContext->ClassDeprecate("Released", "{8280FA20-7171-41BA-ACA1-4F79190DD60F}", &ClassConverters::ConvertToInput);
                serializeContext->ClassDeprecate("OrderedEventCombination", "{D2D4CF91-392E-4D0E-BD60-67E09981F63D}");
                serializeContext->ClassDeprecate("UnorderedEventCombination", "{A1A67F18-C395-45E0-9648-BE01321EB41A}");
                serializeContext->ClassDeprecate("VectorizedEventCombination", "{2BAB18ED-7CA6-4147-B857-9523E653B2A5}");
            }
        }

        void Init() override
        {
            AZ::EnvironmentVariable<ScriptCanvas::NodeRegistry> nodeRegistryVariable = AZ::Environment::FindVariable<ScriptCanvas::NodeRegistry>(ScriptCanvas::s_nodeRegistryName);
            if (nodeRegistryVariable)
            {
                ScriptCanvas::NodeRegistry& nodeRegistry = nodeRegistryVariable.Get();
                Input::InputLibrary::InitNodeRegistry(nodeRegistry);
            }
        }
        void Activate() override { }
        void Deactivate() override { }
    };

    class StartingPointInputModule
        : public AZ::Module
    {
    public:
        AZ_RTTI(StartingPointInputModule, "{B30D421E-127D-4C46-90B1-AC3DDF3EC1D9}", AZ::Module);

        StartingPointInputModule()
            : AZ::Module()
        {
            m_descriptors.insert(m_descriptors.end(), {
                StartingPointInputDummyComponent::CreateDescriptor(),
            });

            AZStd::vector<AZ::ComponentDescriptor*> componentDescriptors(Input::InputLibrary::GetComponentDescriptors());
            m_descriptors.insert(m_descriptors.end(), componentDescriptors.begin(), componentDescriptors.end());
        }

        AZ::ComponentTypeList GetRequiredSystemComponents() const override
        { 
            return AZ::ComponentTypeList({ StartingPointInputDummyComponent::RTTI_Type() }); 
        }
    };
}


namespace ClassConverters
{
    // This method will convert an input based on the SingleEventToAction class (Held, Input, Released, Analog) to an Input
    // all of the data is transferring over, the behavior will now be handled in script
    static bool ConvertToInput(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
    {
        AZ_Error("Input Conversion", classElement.GetNumSubElements() > 0, "You are trying to convert a Held, Pressed, Released, Analog, that is missing it's base class data");
        if (classElement.GetNumSubElements() > 0)
        {
            AZ::SerializeContext::DataElementNode& singleInputToEvent = classElement.GetSubElement(0);
            // store the old data
            AZStd::string deviceType;
            singleInputToEvent.GetChildData(AZ::Crc32("Input Device Type"), deviceType);
            AZStd::string inputName;
            singleInputToEvent.GetChildData(AZ::Crc32("Input Name"), inputName);
            float eventValueMultiplier;
            singleInputToEvent.GetChildData(AZ::Crc32("Event Value Multiplier"), eventValueMultiplier);
            float deadZone;
            singleInputToEvent.GetChildData(AZ::Crc32("Dead Zone"), deadZone);

            // convert the device and input names in case this data wasn't upgraded before the
            // converter from Input v1->v2 was added in Input::ConvertInputVersion1To2 (Input.cpp)
            deviceType = Input::ConvertInputDeviceName(deviceType);
            inputName = Input::ConvertInputEventName(inputName);

            // convert the class
            classElement.Convert(context, AZ::AzTypeInfo<Input::Input>::Uuid());

            //push the data into the new class
            classElement.AddElementWithData(context, "Input Device Type", deviceType);
            classElement.AddElementWithData(context, "Input Name", inputName);
            classElement.AddElementWithData(context, "Event Value Multiplier", eventValueMultiplier);
            classElement.AddElementWithData(context, "Dead Zone", deadZone);
        }
        return true;
    }

    static const int s_deprecatedSingleEventToActionAtVersion = 2;
    static const int s_singleEventToActionVersion = s_deprecatedSingleEventToActionAtVersion + 1;
    static bool BaseClassDeprecator(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
    {
        int currentUpgradedVersion = classElement.GetVersion();
        while (currentUpgradedVersion != s_singleEventToActionVersion)
        {
            switch (currentUpgradedVersion)
            {
                // Going from version 1 to 2 we added the Dead Zone field to the SingleEventToAction class
                case 1:
                {
                    classElement.AddElementWithData(context, "Dead Zone", 0.2f);
                    break;
                }
                case s_deprecatedSingleEventToActionAtVersion:
                {
                    // because we are pulling data out of the base class we need to have a converter for it, and it needs to succeed, or we will lose all of the data
                    return true;
                }
            }
            ++currentUpgradedVersion;
        }
        return true;
    }

    static const int s_deprecatedHeldAtVersion = 2;
    static const int s_heldVersion = s_deprecatedHeldAtVersion + 1;
    static bool ConvertHeldVersion(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
    {
        enum class HeldInvokeType
        {
            EveryDuration,
            OncePerRelease,
            EveryFrameAfterDuration,
        };
        int currentUpgradedVersion = classElement.GetVersion();
        while (currentUpgradedVersion != s_heldVersion)
        {
            switch (currentUpgradedVersion)
            {
                // Going from 1 to 2 we switched from an Invoke Once per release to the HeldInvokeType enum.  Depending on
                // what the previous value of Invoke Once Per Release we would set the enum value to the corresponding option
                case 1:
                {
                    AZ::Crc32 invokeOnceCrc("Invoke Once Per Release");
                    int invokeOncePropertyIndex = classElement.FindElement(invokeOnceCrc);
                    if (invokeOncePropertyIndex != -1)
                    {
                        bool invokeOncePerRelease = true;
                        classElement.GetSubElement(invokeOncePropertyIndex).GetData(invokeOncePerRelease);
                        classElement.ReplaceElement<HeldInvokeType>(context, invokeOncePropertyIndex, "Invoke Type");
                        HeldInvokeType invokeType = HeldInvokeType::OncePerRelease;
                        if (!invokeOncePerRelease)
                        {
                            float holdDuration = 0.1f;
                            classElement.GetChildData(AZ::Crc32("Duration To Hold"), holdDuration);
                            classElement.AddElementWithData(context, "Success Pulse Interval", holdDuration);
                            invokeType = HeldInvokeType::EveryDuration;
                        }
                        classElement.GetSubElement(invokeOncePropertyIndex).SetData(context, invokeType);
                        break;
                    }
                    else
                    {
                        AZ_Warning("Input", false, "Unable to convert Held from version 1 to 2, its data will be lost on save");
                        return false;
                    }
                }
                case s_deprecatedHeldAtVersion:
                {
                    return ConvertToInput(context, classElement);
                }
                default:
                    AZ_Warning("Input", false, "Unable to convert Held: unsupported version %i, its data will be lost on save", currentUpgradedVersion);
                    return false;
            }
            ++currentUpgradedVersion;
        }
        return true;
    }

    static const int s_deprecatedAnalogVersion = 2;
    static const int s_analogVersion = s_deprecatedAnalogVersion + 1;
    static bool ConvertAnalogVersion(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
    {
        int currentUpgradedVersion = classElement.GetVersion();
        while (currentUpgradedVersion != s_analogVersion)
        {
            switch (currentUpgradedVersion)
            {
                case 1:
                {
                    // Going from version 1 to 2 on Analog we removed the Dead Zone field as it was added to the base class
                    // We needed to set the data in the base class to whatever had been set in the analog originally
                    float currentDeadZone = AZ::g_fltEps;
                    classElement.GetChildData(AZ::Crc32("Dead Zone"), currentDeadZone);
                    int singleEventToActionPropertyIndex = classElement.FindElement(AZ::Crc32("BaseClass1"));
                    if (singleEventToActionPropertyIndex != -1)
                    {
                        auto& baseClass = classElement.GetSubElement(singleEventToActionPropertyIndex);
                        if (baseClass.GetVersion() == 2)
                        {
                            int deadZoneIndex = baseClass.FindElement(AZ::Crc32("Dead Zone"));
                            if (deadZoneIndex != -1)
                            {
                                // in case the base class updates before us
                                baseClass.GetSubElement(deadZoneIndex).SetData(context, currentDeadZone);
                            }
                        }
                        break;
                    }
                    else
                    {
                        AZ_Warning("Input", false, "Unable to convert Analog from version 1 to 2, its data will be lost on save");
                        return false;
                    }
                }
                case s_deprecatedAnalogVersion:
                {
                    return ConvertToInput(context, classElement);
                }
                default:
                    AZ_Warning("Input", false, "Unable to convert Analog: unsupported version %i, its data will be lost on save", currentUpgradedVersion);
                    return false;
            }
            ++currentUpgradedVersion;
        }
        return true;
    }
}


// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(StartingPointInput_09f4bedeee614358bc36788e77f97e51, StartingPointInput::StartingPointInputModule)