/*
* 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 "Rain_precompiled.h"
#include "EditorRainComponent.h"

#include <Editor/Objects/BaseObject.h>
#include <Objects/EntityObject.h>
#include <AzCore/RTTI/BehaviorContext.h>

#include <MathConversion.h>

//Legacy for conversion
#include "Rain.h"

namespace Rain
{
    void EditorRainComponent::Reflect(AZ::ReflectContext* context)
    {
        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
        {
            serializeContext->Class<EditorRainComponent, EditorComponentBase>()
                ->Version(2)
                ->Field("Enabled", &EditorRainComponent::m_enabled)
                ->Field("RenderInEditMode", &EditorRainComponent::m_renderInEditMode)
                ->Field("Options", &EditorRainComponent::m_rainOptions)
                ;

            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
            {
                editContext->Class<EditorRainComponent>("Rain", "Defines rain settings for the level ")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::Category, "Environment")
                        ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/Rain.svg")
                        ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/Rain.png")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
                        ->Attribute(AZ::Edit::Attributes::HelpPageURL, "http://docs.aws.amazon.com/console/lumberyard/userguide/rain-component")
                        ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                    ->DataElement(AZ::Edit::UIHandlers::Default, &EditorRainComponent::m_enabled, "Enabled", "Sets if the rain is enabled")
                    ->DataElement(AZ::Edit::UIHandlers::Default, &EditorRainComponent::m_renderInEditMode, "Render In Edit Mode", "Should rain render in edit mode")
                    ->DataElement(AZ::Edit::UIHandlers::Default, &EditorRainComponent::m_rainOptions, "Options", "Options for rain simulation")
                    ;

                editContext->Class<RainOptions>("Rain Options", "Options for rain simulation")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
                        ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                        ->Attribute(AZ::Edit::Attributes::Visibility, AZ_CRC("PropertyVisibility_ShowChildrenOnly", 0xef428f20))

                    ->DataElement(AZ::Edit::UIHandlers::Default, &RainOptions::m_useVisAreas, "Use VisAreas", "Should rain render when the player is inside of a VisArea")
                    ->DataElement(AZ::Edit::UIHandlers::Default, &RainOptions::m_disableOcclusion, "Disable Occlusion", "Should allow objects to block rainfall")
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &RainOptions::m_radius, "Radius", "Radius of the rain area")
                        ->Attribute(AZ::Edit::Attributes::Min, 0.f)
                        ->Attribute(AZ::Edit::Attributes::Max, 10000.f)
                        ->Attribute(AZ::Edit::Attributes::Step, 1.0f)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &RainOptions::m_amount, "Amount", "Overall amount of rain")
                        ->Attribute(AZ::Edit::Attributes::Min, 0.f)
                        ->Attribute(AZ::Edit::Attributes::Max, 100.0f)
                        ->Attribute(AZ::Edit::Attributes::Step, 0.1f)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &RainOptions::m_diffuseDarkening, "Diffuse Darkening", "Amount of darkening due to surface wetness")
                        ->Attribute(AZ::Edit::Attributes::Min, 0.f)
                        ->Attribute(AZ::Edit::Attributes::Max, 1.0f)
                        ->Attribute(AZ::Edit::Attributes::Step, 0.1f)

                    ->ClassElement(AZ::Edit::ClassElements::Group, "Drops")
                        ->Attribute(AZ::Edit::Attributes::AutoExpand, false)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &RainOptions::m_rainDropsAmount, "Amount", "Number of rain drops that can be seen in the air")
                        ->Attribute(AZ::Edit::Attributes::Min, 0.f)
                        ->Attribute(AZ::Edit::Attributes::Max, 100.0f)
                        ->Attribute(AZ::Edit::Attributes::Step, 0.1f)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &RainOptions::m_rainDropsSpeed, "Speed", "Speed of falling rain drops")
                        ->Attribute(AZ::Edit::Attributes::Min, 0.f)
                        ->Attribute(AZ::Edit::Attributes::Max, 100.0f)
                        ->Attribute(AZ::Edit::Attributes::Step, 0.1f)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &RainOptions::m_rainDropsLighting, "Lighting", "Brightness/Back-lighting of rain drops")
                        ->Attribute(AZ::Edit::Attributes::Min, 0.f)
                        ->Attribute(AZ::Edit::Attributes::Max, 100.0f)
                        ->Attribute(AZ::Edit::Attributes::Step, 0.1f)

                    ->ClassElement(AZ::Edit::ClassElements::Group, "Puddles")
                        ->Attribute(AZ::Edit::Attributes::AutoExpand, false)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &RainOptions::m_puddlesAmount, "Amount", "Depth and brightness of puddles generated")
                        ->Attribute(AZ::Edit::Attributes::Min, 0.f)
                        ->Attribute(AZ::Edit::Attributes::Max, 10.0f)
                        ->Attribute(AZ::Edit::Attributes::Step, 0.01f)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &RainOptions::m_puddlesMaskAmount, "Mask Amount", "Strength of the puddle mask")
                        ->Attribute(AZ::Edit::Attributes::Min, 0.f)
                        ->Attribute(AZ::Edit::Attributes::Max, 1.0f)
                        ->Attribute(AZ::Edit::Attributes::Step, 0.01f)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &RainOptions::m_puddlesRippleAmount, "Ripple Amount", "Strength and frequency of ripples in puddles")
                        ->Attribute(AZ::Edit::Attributes::Min, 0.f)
                        ->Attribute(AZ::Edit::Attributes::Max, 100.0f)
                        ->Attribute(AZ::Edit::Attributes::Step, 0.1f)
                    ->DataElement(AZ::Edit::UIHandlers::Slider, &RainOptions::m_splashesAmount, "Splashes Amount", "Strength of the splash effect generated by rain drops")
                        ->Attribute(AZ::Edit::Attributes::Min, 0.f)
                        ->Attribute(AZ::Edit::Attributes::Max, 1000.0f)
                        ->Attribute(AZ::Edit::Attributes::Step, 0.1f)
                    ;
            }
        }

        if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
        {
            behaviorContext->Class<EditorRainComponent>()->RequestBus("RainComponentRequestBus");
        }
    }

    void EditorRainComponent::Activate()
    {
        EditorComponentBase::Activate();

        AzToolsFramework::Components::EditorComponentBase::Activate();
        AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId());
        Rain::RainComponentRequestBus::Handler::BusConnect(GetEntityId());
        AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(GetEntityId());

        m_currentWorldPos = AZ::Vector3::CreateZero();
        AZ::TransformBus::EventResult(m_currentWorldPos, GetEntityId(), &AZ::TransformBus::Events::GetWorldTranslation);

        m_currentWorldTransform = AZ::Transform::CreateIdentity();
        AZ::TransformBus::EventResult(m_currentWorldTransform, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
        AZ::TickBus::Handler::BusConnect();
    }

    void EditorRainComponent::Deactivate()
    {
        AZ::TransformNotificationBus::Handler::BusDisconnect(GetEntityId());
        AzToolsFramework::Components::EditorComponentBase::Deactivate();
        Rain::RainComponentRequestBus::Handler::BusDisconnect(GetEntityId());
        AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect(GetEntityId());

        AZ::TickBus::Handler::BusDisconnect();
        TurnOffRain();
        Rain::RainComponentRequestBus::Broadcast(&Rain::RainComponentRequestBus::Events::UpdateRain);

        EditorComponentBase::Deactivate();
    }

    void EditorRainComponent::OnTransformChanged(const AZ::Transform& /*local*/, const AZ::Transform& world)
    {
        m_currentWorldPos = world.GetPosition();
        m_currentWorldTransform = world;
    }

    void EditorRainComponent::Enable()
    {
        m_enabled = true;
        UpdateRain();
    }

    void EditorRainComponent::Disable()
    {
        m_enabled = false;
        UpdateRain();
    }

    void EditorRainComponent::Toggle()
    {
        m_enabled = !m_enabled;
        UpdateRain();
    }

    bool EditorRainComponent::IsEnabled()
    {
        return m_enabled;
    }

    void EditorRainComponent::SetEnabled(bool enabled)
    {
        m_enabled = enabled;
        UpdateRain();
    }

    bool EditorRainComponent::GetUseVisAreas()
    {
        return m_rainOptions.m_useVisAreas;
    }

    void EditorRainComponent::SetUseVisAreas(bool useVisAreas)
    {
        m_rainOptions.m_useVisAreas = useVisAreas;
        UpdateRain();
    }

    bool EditorRainComponent::GetDisableOcclusion()
    {
        return m_rainOptions.m_disableOcclusion;
    }

    void EditorRainComponent::SetDisableOcclusion(bool disableOcclusion)
    {
        m_rainOptions.m_disableOcclusion = disableOcclusion;
        UpdateRain();
    }

    float EditorRainComponent::GetRadius()
    {
        return m_rainOptions.m_radius;
    }

    void EditorRainComponent::SetRadius(float radius)
    {
        m_rainOptions.m_radius = radius;
        UpdateRain();
    }

    float EditorRainComponent::GetAmount()
    {
        return m_rainOptions.m_amount;
    }

    void EditorRainComponent::SetAmount(float amount)
    {
        m_rainOptions.m_amount = amount;
        UpdateRain();
    }

    float EditorRainComponent::GetDiffuseDarkening()
    {
        return m_rainOptions.m_diffuseDarkening;
    }

    void EditorRainComponent::SetDiffuseDarkening(float diffuseDarkening)
    {
        m_rainOptions.m_diffuseDarkening = diffuseDarkening;
        UpdateRain();
    }

    float EditorRainComponent::GetRainDropsAmount()
    {
        return m_rainOptions.m_rainDropsAmount;
    }

    void EditorRainComponent::SetRainDropsAmount(float rainDropsAmount)
    {
        m_rainOptions.m_rainDropsAmount = rainDropsAmount;
        UpdateRain();
    }

    float EditorRainComponent::GetRainDropsSpeed()
    {
        return m_rainOptions.m_rainDropsSpeed;
    }

    void EditorRainComponent::SetRainDropsSpeed(float rainDropsSpeed)
    {
        m_rainOptions.m_rainDropsSpeed = rainDropsSpeed;
        UpdateRain();
    }

    float EditorRainComponent::GetRainDropsLighting()
    {
        return m_rainOptions.m_rainDropsLighting;
    }

    void EditorRainComponent::SetRainDropsLighting(float rainDropsLighting)
    {
        m_rainOptions.m_rainDropsLighting = rainDropsLighting;
        UpdateRain();
    }

    float EditorRainComponent::GetPuddlesAmount()
    {
        return m_rainOptions.m_puddlesAmount;
    }

    void EditorRainComponent::SetPuddlesAmount(float puddlesAmount)
    {
        m_rainOptions.m_puddlesAmount = puddlesAmount;
        UpdateRain();
    }

    float EditorRainComponent::GetPuddlesMaskAmount()
    {
        return m_rainOptions.m_puddlesMaskAmount;
    }

    void EditorRainComponent::SetPuddlesMaskAmount(float puddlesMaskAmount)
    {
        m_rainOptions.m_puddlesMaskAmount = puddlesMaskAmount;
        UpdateRain();
    }

    float EditorRainComponent::GetPuddlesRippleAmount()
    {
        return m_rainOptions.m_puddlesRippleAmount;
    }

    void EditorRainComponent::SetPuddlesRippleAmount(float puddlesRippleAmount)
    {
        m_rainOptions.m_puddlesRippleAmount = puddlesRippleAmount;
        UpdateRain();
    }

    float EditorRainComponent::GetSplashesAmount()
    {
        return m_rainOptions.m_splashesAmount;
    }

    void EditorRainComponent::SetSplashesAmount(float splashesAmount)
    {
        m_rainOptions.m_splashesAmount = splashesAmount;
        UpdateRain();
    }

    RainOptions EditorRainComponent::GetRainOptions()
    {
        return m_rainOptions;
    }

    void EditorRainComponent::SetRainOptions(RainOptions rainOptions)
    {
        m_rainOptions = rainOptions;
        UpdateRain();
    }

    void EditorRainComponent::UpdateRain()
    {
        //If the rain component is disabled, set the rain amount to 0
        if (!m_enabled)
        {
            TurnOffRain();
            return;
        }

        UpdateRainSettings(m_currentWorldTransform, m_rainOptions);
    }

    void EditorRainComponent::BuildGameEntity(AZ::Entity* gameEntity)
    {
        if (auto component = gameEntity->CreateComponent<RainComponent>())
        {
            component->m_enabled = m_enabled;
            component->m_rainOptions = m_rainOptions;
        }
    }

    void EditorRainComponent::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/)
    {
        if (!m_enabled || (!m_renderInEditMode && gEnv->IsEditor() && !gEnv->IsEditorSimulationMode() && !gEnv->IsEditorGameMode()))
        {
            TurnOffRain();
            return;
        }

        UpdateRainSettings(m_currentWorldTransform, m_rainOptions);
    }

    void EditorRainComponent::DisplayEntityViewport(
        const AzFramework::ViewportInfo& viewportInfo,
        AzFramework::DebugDisplayRequests& debugDisplay)
    {
        debugDisplay.SetColor(AZ::Vector4(1.0f, 1.0f, 1.0f, 1.0f));
        debugDisplay.DrawWireSphere(m_currentWorldPos, m_rainOptions.m_radius);
    }

    AZ::LegacyConversion::LegacyConversionResult RainConverter::ConvertEntity(CBaseObject* pEntityToConvert)
    {
        if (!((AZStd::string(pEntityToConvert->metaObject()->className()) == "CRain" && AZStd::string(pEntityToConvert->metaObject()->className()) != "CEntityObject") ||
            (pEntityToConvert->inherits("CEntityObject") && static_cast<CEntityObject *>(pEntityToConvert)->GetEntityClass() == "Rain")))
        {
            // We don't know how to convert this entity, whatever it is
            return AZ::LegacyConversion::LegacyConversionResult::Ignored;
        }

        //Get the underlying entity object
        CEntityObject* entityObject = static_cast<CEntityObject*>(pEntityToConvert);
        if (!entityObject)
        {
            //Object was not an entity object but we did not ignore the object
            return AZ::LegacyConversion::LegacyConversionResult::Failed;
        }

        //Retrieve the underlying legacy CRain object
        using namespace LYGame;
        IEntity* legacyEntity = entityObject->GetIEntity();
        AZStd::shared_ptr<CRain> rain = legacyEntity->GetComponent<CRain>();
        if (!rain)
        {
            //Entity did not have a CRain component
            return AZ::LegacyConversion::LegacyConversionResult::Failed;
        }

        // Step 1, create an entity with no components.

        // if we have a parent, and the parent is not yet converted, we ignore this entity!
        // this is not ALWAYS the case - there may be types you wish to create anyway and just put them in the world, so we cannot enforce this
        // inside the CreateConvertedEntity function.
        AZ::Outcome<AZ::Entity*, AZ::LegacyConversion::CreateEntityResult> result(nullptr);
        AZ::LegacyConversion::LegacyConversionRequestBus::BroadcastResult(result, &AZ::LegacyConversion::LegacyConversionRequests::CreateConvertedEntity, pEntityToConvert, true, AZ::ComponentTypeList());

        if (!result.IsSuccess())
        {
            // if we failed due to no parent, we keep processing
            return (result.GetError() == AZ::LegacyConversion::CreateEntityResult::FailedNoParent) ? AZ::LegacyConversion::LegacyConversionResult::Ignored : AZ::LegacyConversion::LegacyConversionResult::Failed;
        }
        AZ::Entity *entity = result.GetValue();
        entity->Deactivate();

        EditorRainComponent *component = entity->CreateComponent<EditorRainComponent>();

        if (!component)
        {
            return AZ::LegacyConversion::LegacyConversionResult::Failed;
        }

        // Step 2: extract the parameter data from the underlying CRain object into
        // the component.

        // Because the rain object is a Lua based entity this is the easiest way to retrieve its parameters

        SRainParams legacyParams = rain->GetRainParams();
        Rain::RainOptions options;

        options.m_useVisAreas = !legacyParams.bIgnoreVisareas;
        options.m_disableOcclusion = legacyParams.bDisableOcclusion;
        options.m_radius = legacyParams.fRadius;
        options.m_amount = legacyParams.fAmount;
        options.m_diffuseDarkening = legacyParams.fDiffuseDarkening;
        options.m_puddlesAmount = legacyParams.fPuddlesAmount;
        options.m_puddlesMaskAmount = legacyParams.fPuddlesMaskAmount;
        options.m_puddlesRippleAmount = legacyParams.fPuddlesRippleAmount;
        options.m_rainDropsAmount = legacyParams.fRainDropsAmount;
        options.m_rainDropsSpeed = legacyParams.fRainDropsSpeed;
        options.m_rainDropsLighting = legacyParams.fRainDropsLighting;
        options.m_splashesAmount = legacyParams.fSplashesAmount;

        component->m_enabled = rain->GetEnabled();
        component->m_rainOptions = options;

        // Turn the entity back on before wrapping up.
        entity->Activate();

        // finally, we can delete the old entity since we handled this completely!
        AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::AddDirtyEntity, result.GetValue()->GetId());
        return AZ::LegacyConversion::LegacyConversionResult::HandledDeleteEntity;
    }

    bool RainConverter::BeforeConversionBegins()
    {
        return true;
    }

    bool RainConverter::AfterConversionEnds()
    {
        return true;
    }
}