// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.

#pragma once

#if COMPILE_WITH_MODEL_TOOL

#include "Engine/Core/Config.h"
#include "Engine/Serialization/ISerializable.h"
#include "Engine/Graphics/Models/ModelData.h"
#include "Engine/Graphics/Models/SkeletonData.h"
#include "Engine/Animations/AnimationData.h"

class JsonWriter;

/// <summary>
/// The model file import data types (used as flags).
/// </summary>
enum class ImportDataTypes : int32
{
    /// <summary>
    /// Imports materials and meshes.
    /// </summary>
    Geometry = 1 << 0,

    /// <summary>
    /// Imports the skeleton bones hierarchy.
    /// </summary>
    Skeleton = 1 << 1,

    /// <summary>
    /// Imports the animations.
    /// </summary>
    Animations = 1 << 2,

    /// <summary>
    /// Imports the scene nodes hierarchy.
    /// </summary>
    Nodes = 1 << 3,

    /// <summary>
    /// Imports the materials.
    /// </summary>
    Materials = 1 << 4,

    /// <summary>
    /// Imports the textures.
    /// </summary>
    Textures = 1 << 5,
};

DECLARE_ENUM_OPERATORS(ImportDataTypes);

/// <summary>
/// Imported model data container. Represents unified model source file data (meshes, animations, skeleton, materials).
/// </summary>
class ImportedModelData
{
public:

    struct LOD
    {
        Array<MeshData*> Meshes;

        BoundingBox GetBox() const;
    };

    struct Node
    {
        /// <summary>
        /// The parent node index. The root node uses value -1.
        /// </summary>
        int32 ParentIndex;

        /// <summary>
        /// The local transformation of the node, relative to the parent node.
        /// </summary>
        Transform LocalTransform;

        /// <summary>
        /// The name of this node.
        /// </summary>
        String Name;
    };

public:

    /// <summary>
    /// The import data types types.
    /// </summary>
    ImportDataTypes Types;

    /// <summary>
    /// The textures slots.
    /// </summary>
    Array<TextureEntry> Textures;

    /// <summary>
    /// The material slots.
    /// </summary>
    Array<MaterialSlotEntry> Materials;

    /// <summary>
    /// The level of details data.
    /// </summary>
    Array<LOD> LODs;

    /// <summary>
    /// The skeleton data.
    /// </summary>
    SkeletonData Skeleton;

    /// <summary>
    /// The scene nodes.
    /// </summary>
    Array<Node> Nodes;

    /// <summary>
    /// The node animations.
    /// </summary>
    AnimationData Animation;

public:

    /// <summary>
    /// Initializes a new instance of the <see cref="ImportedModelData"/> class.
    /// </summary>
    /// <param name="types">The types.</param>
    ImportedModelData(ImportDataTypes types)
    {
        Types = types;
    }

    /// <summary>
    /// Finalizes an instance of the <see cref="ImportedModelData"/> class.
    /// </summary>
    ~ImportedModelData()
    {
        // Ensure to cleanup data
        for (int32 i = 0; i < LODs.Count(); i++)
            LODs[i].Meshes.ClearDelete();
    }
};

/// <summary>
/// Import models and animations helper.
/// </summary>
class FLAXENGINE_API ModelTool
{
public:

    /// <summary>
    /// Declares the imported data type.
    /// </summary>
    DECLARE_ENUM_EX_3(ModelType, int32, 0, Model, SkinnedModel, Animation);

    /// <summary>
    /// Declares the imported animation clip duration.
    /// </summary>
    DECLARE_ENUM_EX_2(AnimationDuration, int32, 0, Imported, Custom);

    /// <summary>
    /// Importing model options
    /// </summary>
    struct Options : public ISerializable
    {
        ModelType Type = ModelType::Model;

        // Geometry
        bool CalculateNormals = false;
        float SmoothingNormalsAngle = 175.0f;
        bool FlipNormals = false;
        float SmoothingTangentsAngle = 45.0f;
        bool CalculateTangents = true;
        bool OptimizeMeshes = true;
        bool MergeMeshes = true;
        bool ImportLODs = true;
        bool ImportVertexColors = true;
        bool ImportBlendShapes = false;
        ModelLightmapUVsSource LightmapUVsSource = ModelLightmapUVsSource::Disable;

        // Transform
        float Scale = 1.0f;
        Quaternion Rotation = Quaternion::Identity;
        Vector3 Translation = Vector3::Zero;
        bool CenterGeometry = false;

        // Animation
        AnimationDuration Duration = AnimationDuration::Imported;
        Vector2 FramesRange = Vector2::Zero;
        float DefaultFrameRate = 0.0f;
        float SamplingRate = 0.0f;
        bool SkipEmptyCurves = true;
        bool OptimizeKeyframes = true;
        bool EnableRootMotion = false;
        String RootNodeName;
        int32 AnimationIndex = -1;

        // Level Of Detail
        bool GenerateLODs = false;
        int32 BaseLOD = 0;
        int32 LODCount = 4;
        float TriangleReduction = 0.5f;

        // Materials
        bool ImportMaterials = true;
        bool ImportTextures = true;
        bool RestoreMaterialsOnReimport = true;

    public:

        // [ISerializable]
        void Serialize(SerializeStream& stream, const void* otherObj) override;
        void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
    };

public:

    /// <summary>
    /// Imports the model source file data.
    /// </summary>
    /// <param name="path">The file path.</param>
    /// <param name="data">The output data.</param>
    /// <param name="options">The import options.</param>
    /// <param name="errorMsg">The error message container.</param>
    /// <returns>True if fails, otherwise false.</returns>
    static bool ImportData(const String& path, ImportedModelData& data, Options options, String& errorMsg);

    /// <summary>
    /// Imports the model.
    /// </summary>
    /// <param name="path">The file path.</param>
    /// <param name="meshData">The output data.</param>
    /// <param name="options">The import options.</param>
    /// <param name="errorMsg">The error message container.</param>
    /// <param name="autoImportOutput">The output folder for the additional imported data - optional. Used to auto-import textures and material assets.</param>
    /// <returns>True if fails, otherwise false.</returns>
    static bool ImportModel(const String& path, ModelData& meshData, Options options, String& errorMsg, const String& autoImportOutput = String::Empty);

public:

    static int32 DetectLodIndex(const String& nodeName);
    static bool FindTexture(const String& sourcePath, const String& file, String& path);

    /// <summary>
    /// Gets the local transformations to go from rootIndex to index.
    /// </summary>
    /// <param name="nodes">The nodes containing the local transformations.</param>
    /// <param name="rootIndex">The root index.</param>
    /// <param name="index">The current index.</param>
    /// <returns>The transformation at this index.</returns>
    template<typename Node>
    static Transform CombineTransformsFromNodeIndices(Array<Node>& nodes, int32 rootIndex, int32 index)
    {
        if (index == -1 || index == rootIndex)
            return Transform::Identity;

        auto result = nodes[index].LocalTransform;
        if (index != rootIndex)
        {
            const auto parentTransform = CombineTransformsFromNodeIndices(nodes, rootIndex, nodes[index].ParentIndex);
            result = parentTransform.LocalToWorld(result);
        }

        return result;
    }

private:

#if USE_ASSIMP
    static bool ImportDataAssimp(const char* path, ImportedModelData& data, const Options& options, String& errorMsg);
#endif
#if USE_AUTODESK_FBX_SDK
	static bool ImportDataAutodeskFbxSdk(const char* path, ImportedModelData& data, const Options& options, String& errorMsg);
#endif
#if USE_OPEN_FBX
    static bool ImportDataOpenFBX(const char* path, ImportedModelData& data, const Options& options, String& errorMsg);
#endif
};

#endif
