/*
* 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 <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>

#include <AzToolsFramework/API/ViewPaneOptions.h>
#include <LyViewPaneNames.h>

// LmbrCentral
#include <LmbrCentral/Shape/BoxShapeComponentBus.h>
#include <LmbrCentral/Shape/CapsuleShapeComponentBus.h>
#include <LmbrCentral/Shape/CompoundShapeComponentBus.h>
#include <LmbrCentral/Shape/CylinderShapeComponentBus.h>
#include <LmbrCentral/Shape/DiskShapeComponentBus.h>
#include <LmbrCentral/Shape/PolygonPrismShapeComponentBus.h>
#include <LmbrCentral/Shape/SphereShapeComponentBus.h>
#include <LmbrCentral/Shape/TubeShapeComponentBus.h>

// Gradient Signal
#include <GradientSignal/Editor/EditorGradientTypeIds.h>

// Vegetation
#include <Vegetation/Editor/EditorVegetationComponentTypeIds.h>

// Graph Model
#include <GraphModel/Integration/NodePalette/StandardNodePaletteItem.h>

// Landscape Canvas
#include "LandscapeCanvasSystemComponent.h"
#include <Editor/MainWindow.h>
#include <Editor/Core/GraphContext.h>
#include <Editor/Nodes/Areas/AreaBlenderNode.h>
#include <Editor/Nodes/Areas/BlockerAreaNode.h>
#include <Editor/Nodes/Areas/MeshBlockerAreaNode.h>
#include <Editor/Nodes/Areas/SpawnerAreaNode.h>
#include <Editor/Nodes/Areas/StaticVegetationBlockerAreaNode.h>
#include <Editor/Nodes/AreaFilters/AltitudeFilterNode.h>
#include <Editor/Nodes/AreaFilters/DistanceBetweenFilterNode.h>
#include <Editor/Nodes/AreaFilters/DistributionFilterNode.h>
#include <Editor/Nodes/AreaFilters/ShapeIntersectionFilterNode.h>
#include <Editor/Nodes/AreaFilters/SlopeFilterNode.h>
#include <Editor/Nodes/AreaFilters/SurfaceMaskDepthFilterNode.h>
#include <Editor/Nodes/AreaFilters/SurfaceMaskFilterNode.h>
#include <Editor/Nodes/AreaModifiers/PositionModifierNode.h>
#include <Editor/Nodes/AreaModifiers/RotationModifierNode.h>
#include <Editor/Nodes/AreaModifiers/ScaleModifierNode.h>
#include <Editor/Nodes/AreaModifiers/SlopeAlignmentModifierNode.h>
#include <Editor/Nodes/AreaSelectors/AssetWeightSelectorNode.h>
#include <Editor/Nodes/Gradients/AltitudeGradientNode.h>
#include <Editor/Nodes/Gradients/ConstantGradientNode.h>
#include <Editor/Nodes/Gradients/FastNoiseGradientNode.h>
#include <Editor/Nodes/Gradients/ImageGradientNode.h>
#include <Editor/Nodes/Gradients/PerlinNoiseGradientNode.h>
#include <Editor/Nodes/Gradients/RandomNoiseGradientNode.h>
#include <Editor/Nodes/Gradients/ShapeAreaFalloffGradientNode.h>
#include <Editor/Nodes/Gradients/SlopeGradientNode.h>
#include <Editor/Nodes/Gradients/SurfaceMaskGradientNode.h>
#include <Editor/Nodes/GradientModifiers/DitherGradientModifierNode.h>
#include <Editor/Nodes/GradientModifiers/GradientMixerNode.h>
#include <Editor/Nodes/GradientModifiers/InvertGradientModifierNode.h>
#include <Editor/Nodes/GradientModifiers/LevelsGradientModifierNode.h>
#include <Editor/Nodes/GradientModifiers/PosterizeGradientModifierNode.h>
#include <Editor/Nodes/GradientModifiers/SmoothStepGradientModifierNode.h>
#include <Editor/Nodes/GradientModifiers/ThresholdGradientModifierNode.h>
#include <Editor/Nodes/Shapes/BoxShapeNode.h>
#include <Editor/Nodes/Shapes/CapsuleShapeNode.h>
#include <Editor/Nodes/Shapes/CompoundShapeNode.h>
#include <Editor/Nodes/Shapes/CylinderShapeNode.h>
#include <Editor/Nodes/Shapes/DiskShapeNode.h>
#include <Editor/Nodes/Shapes/PolygonPrismShapeNode.h>
#include <Editor/Nodes/Shapes/SphereShapeNode.h>
#include <Editor/Nodes/Shapes/TubeShapeNode.h>

namespace LandscapeCanvas
{
    // Define all of our supported nodes with their corresponding Component TypeId
    // so we can use these mappings for registration and factory method creation
#define LANDSCAPE_CANVAS_NODE_TABLE(VISITOR_FUNCTION, ...)    \
    /* Area nodes */    \
    VISITOR_FUNCTION<AreaBlenderNode>(Vegetation::EditorAreaBlenderComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<BlockerAreaNode>(Vegetation::EditorBlockerComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<MeshBlockerAreaNode>(Vegetation::EditorMeshBlockerComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<SpawnerAreaNode>(Vegetation::EditorSpawnerComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<StaticVegetationBlockerAreaNode>(Vegetation::EditorStaticVegetationBlockerComponentTypeId, ##__VA_ARGS__);     \
    /* Area filter nodes */ \
    VISITOR_FUNCTION<AltitudeFilterNode>(Vegetation::EditorSurfaceAltitudeFilterComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<DistanceBetweenFilterNode>(Vegetation::EditorDistanceBetweenFilterComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<DistributionFilterNode>(Vegetation::EditorDistributionFilterComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<ShapeIntersectionFilterNode>(Vegetation::EditorShapeIntersectionFilterComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<SlopeFilterNode>(Vegetation::EditorSurfaceSlopeFilterComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<SurfaceMaskDepthFilterNode>(Vegetation::EditorSurfaceMaskDepthFilterComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<SurfaceMaskFilterNode>(Vegetation::EditorSurfaceMaskFilterComponentTypeId, ##__VA_ARGS__);     \
    /* Area modifier nodes */    \
    VISITOR_FUNCTION<PositionModifierNode>(Vegetation::EditorPositionModifierComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<RotationModifierNode>(Vegetation::EditorRotationModifierComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<ScaleModifierNode>(Vegetation::EditorScaleModifierComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<SlopeAlignmentModifierNode>(Vegetation::EditorSlopeAlignmentModifierComponentTypeId, ##__VA_ARGS__);     \
    /* Area selector nodes */    \
    VISITOR_FUNCTION<AssetWeightSelectorNode>(Vegetation::EditorDescriptorWeightSelectorComponentTypeId, ##__VA_ARGS__);     \
    /* Shape nodes */    \
    VISITOR_FUNCTION<BoxShapeNode>(LmbrCentral::EditorBoxShapeComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<CapsuleShapeNode>(LmbrCentral::EditorCapsuleShapeComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<CompoundShapeNode>(LmbrCentral::EditorCompoundShapeComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<CylinderShapeNode>(LmbrCentral::EditorCylinderShapeComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<DiskShapeNode>(LmbrCentral::EditorDiskShapeComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<PolygonPrismShapeNode>(LmbrCentral::EditorPolygonPrismShapeComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<SphereShapeNode>(LmbrCentral::EditorSphereShapeComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<TubeShapeNode>(LmbrCentral::EditorTubeShapeComponentTypeId, ##__VA_ARGS__);     \
    /* Gradient generator nodes */    \
    VISITOR_FUNCTION<FastNoiseGradientNode>(EditorFastNoiseGradientComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<PerlinNoiseGradientNode>(GradientSignal::EditorPerlinGradientComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<RandomNoiseGradientNode>(GradientSignal::EditorRandomGradientComponentTypeId, ##__VA_ARGS__);     \
    /* Gradient nodes */    \
    VISITOR_FUNCTION<AltitudeGradientNode>(GradientSignal::EditorSurfaceAltitudeGradientComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<ConstantGradientNode>(GradientSignal::EditorConstantGradientComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<ImageGradientNode>(GradientSignal::EditorImageGradientComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<ShapeAreaFalloffGradientNode>(GradientSignal::EditorShapeAreaFalloffGradientComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<SlopeGradientNode>(GradientSignal::EditorSurfaceSlopeGradientComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<SurfaceMaskGradientNode>(GradientSignal::EditorSurfaceMaskGradientComponentTypeId, ##__VA_ARGS__);     \
    /* Gradient modifier nodes */    \
    VISITOR_FUNCTION<DitherGradientModifierNode>(GradientSignal::EditorDitherGradientComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<GradientMixerNode>(GradientSignal::EditorMixedGradientComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<InvertGradientModifierNode>(GradientSignal::EditorInvertGradientComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<LevelsGradientModifierNode>(GradientSignal::EditorLevelsGradientComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<PosterizeGradientModifierNode>(GradientSignal::EditorPosterizeGradientComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<SmoothStepGradientModifierNode>(GradientSignal::EditorSmoothStepGradientComponentTypeId, ##__VA_ARGS__);     \
    VISITOR_FUNCTION<ThresholdGradientModifierNode>(GradientSignal::EditorThresholdGradientComponentTypeId, ##__VA_ARGS__);     \

    template<typename NodeType>
    void RegisterNode(const AZ::TypeId& typeId, AZ::ReflectContext* context)
    {
        GraphModelIntegration::ReflectAndCreateNodeMimeEvent<NodeType>(context);
    }

    void LandscapeCanvasSystemComponent::NotifyRegisterViews()
    {
        AzToolsFramework::ViewPaneOptions options;
        options.paneRect = QRect(100, 100, 1280, 1024);
        options.sendViewPaneNameBackToAmazonAnalyticsServers = true;
        AzToolsFramework::RegisterViewPane<LandscapeCanvasEditor::MainWindow>(LyViewPane::LandscapeCanvas, LyViewPane::CategoryTools, options);
    }

    LandscapeCanvasSystemComponent::LandscapeCanvasSystemComponent()
    {
        LandscapeCanvas::GraphContext::SetInstance(AZStd::make_shared<LandscapeCanvas::GraphContext>());

        // Register factory methods for creating for all supported nodes
        LANDSCAPE_CANVAS_NODE_TABLE(RegisterFactoryMethod);
    }

    LandscapeCanvasSystemComponent::~LandscapeCanvasSystemComponent()
    {
        LandscapeCanvas::GraphContext::SetInstance(nullptr);
        AzToolsFramework::CloseViewPane(LyViewPane::LandscapeCanvas);
        AzToolsFramework::UnregisterViewPane(LyViewPane::LandscapeCanvas);
    }

    void LandscapeCanvasSystemComponent::Reflect(AZ::ReflectContext* context)
    {
        // Reflect all our base node types so they can be serialized/deserialized, since
        // the below LANDSCAPE_CANVAS_NODE_TABLE macro only reflects the actual concrete classes
        BaseNode::Reflect(context);
        BaseAreaFilterNode::Reflect(context);
        BaseAreaModifierNode::Reflect(context);
        BaseAreaNode::Reflect(context);
        BaseGradientModifierNode::Reflect(context);
        BaseGradientNode::Reflect(context);
        BaseShapeNode::Reflect(context);

        // Reflect and create the node mime events for all our supported nodes
        LANDSCAPE_CANVAS_NODE_TABLE(RegisterNode, context);

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

            if (AZ::EditContext* ec = serialize->GetEditContext())
            {
                ec->Class<LandscapeCanvasSystemComponent>("LandscapeCanvas", "Graph canvas representation of Dynamic Vegetation")
                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
                        ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
                    ;
            }
        }

        if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
        {
            behaviorContext->ConstantProperty("LANDSCAPE_CANVAS_EDITOR_ID", []() { return LANDSCAPE_CANVAS_EDITOR_ID; })
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);

            behaviorContext->EBus<LandscapeCanvasNodeFactoryRequestBus>("LandscapeCanvasNodeFactoryRequestBus")
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
                ->Attribute(AZ::Script::Attributes::Category, "Editor")
                ->Attribute(AZ::Script::Attributes::Module, "landscapecanvas")
                ->Event("CreateNodeForTypeName", &LandscapeCanvasNodeFactoryRequests::CreateNodeForTypeName)
                ;

            behaviorContext->EBus<LandscapeCanvasRequestBus>("LandscapeCanvasRequestBus")
                ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
                ->Attribute(AZ::Script::Attributes::Category, "Editor")
                ->Attribute(AZ::Script::Attributes::Module, "landscapecanvas")
                ->Event("OnGraphEntity", &LandscapeCanvasRequests::OnGraphEntity)
                ->Event("GetNodeMatchingEntityInGraph", &LandscapeCanvasRequests::GetNodeMatchingEntityInGraph)
                ->Event("GetNodeMatchingEntityComponentInGraph", &LandscapeCanvasRequests::GetNodeMatchingEntityComponentInGraph)
                ->Event("GetAllNodesMatchingEntity", &LandscapeCanvasRequests::GetAllNodesMatchingEntity)
                ->Event("GetAllNodesMatchingEntityComponent", &LandscapeCanvasRequests::GetAllNodesMatchingEntityComponent)
                ;
        }
    }

#undef LANDSCAPE_CANVAS_NODE_TABLE

    void LandscapeCanvasSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
    {
        provided.push_back(AZ_CRC("LandscapeCanvasService", 0x31668887));
    }

    void LandscapeCanvasSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
    {
        incompatible.push_back(AZ_CRC("LandscapeCanvasService", 0x31668887));
    }

    void LandscapeCanvasSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
    {
        AZ_UNUSED(required);
    }

    void LandscapeCanvasSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
    {
        AZ_UNUSED(dependent);
    }

    void LandscapeCanvasSystemComponent::Init()
    {
        AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
    }

    void LandscapeCanvasSystemComponent::Activate()
    {
        AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
        LandscapeCanvasNodeFactoryRequestBus::Handler::BusConnect();
        LandscapeCanvasSerializationRequestBus::Handler::BusConnect();
    }

    void LandscapeCanvasSystemComponent::Deactivate()
    {
        LandscapeCanvasSerializationRequestBus::Handler::BusDisconnect();
        LandscapeCanvasNodeFactoryRequestBus::Handler::BusDisconnect();
        AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
    }

    BaseNode::BaseNodePtr LandscapeCanvasSystemComponent::CreateNodeForType(GraphModel::GraphPtr graph, const AZ::TypeId& typeId)
    {
        auto it = m_nodeFactory.find(typeId);
        if (it == m_nodeFactory.end())
        {
            return nullptr;
        }

        return it->second(graph);
    }

    GraphModel::NodePtr LandscapeCanvasSystemComponent::CreateNodeForTypeName(GraphModel::GraphPtr graph, AZStd::string_view nodeName)
    {
        AZ::TypeId nodeTypeId = AZ::TypeId::CreateNull();

        // Search through all registered Nodes to find the TypeId for one that
        // matches the requested class name.
        m_serializeContext->EnumerateDerived<GraphModel::Node>(
            [&nodeTypeId, nodeName](const AZ::SerializeContext::ClassData* componentClass, const AZ::Uuid& knownType) -> bool
        {
            AZ_UNUSED(knownType);

            if (componentClass->m_name == nodeName)
            {
                nodeTypeId = componentClass->m_typeId;
                return false;
            }

            return true;
        });

        if (!nodeTypeId.IsNull())
        {
            AZ::TypeId componentTypeId = GetComponentTypeId(nodeTypeId);
            return CreateNodeForType(graph, componentTypeId);
        }

        return nullptr;
    }

    const AZ::TypeId LandscapeCanvasSystemComponent::GetComponentTypeId(const AZ::TypeId& nodeTypeId)
    {
        auto it = m_nodeComponentTypeIds.find(nodeTypeId);
        if (it == m_nodeComponentTypeIds.end())
        {
            return AZ::TypeId();
        }

        return it->second;
    }

    const LandscapeCanvasSerialization& LandscapeCanvasSystemComponent::GetSerializedMappings()
    {
        return m_serialization;
    }

    void LandscapeCanvasSystemComponent::SetSerializedNodeEntities(const AZStd::unordered_map<AZ::EntityId, AZ::Entity*>& nodeEntities)
    {
        // Delete any entities we had previously serialized before updating our mappings
        for (auto it : m_serialization.m_serializedNodeEntities)
        {
            delete it.second;
        }

        m_serialization.m_serializedNodeEntities = nodeEntities;
    }

    void LandscapeCanvasSystemComponent::SetDeserializedEntities(const AZStd::unordered_map<AZ::EntityId, AZ::EntityId>& entities)
    {
        m_serialization.m_deserializedEntities = entities;
    }
}