/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.

#include "pch.h"
#include "CharacterToolSystem.h"
#include "EntryList.h"
#include "ExplorerFileList.h"
#include "SkeletonContent.h"
#include "SkeletonList.h"
#include <ICryAnimation.h>

namespace CharacterTool
{
    void SkeletonContent::Serialize(IArchive& ar)
    {
        Serialization::SContext<SkeletonParameters> context(ar, &skeletonParameters);
        IDefaultSkeleton* skeleton = gEnv->pCharacterManager->LoadModelSKELUnsafeManualRef(skeletonParameters.skeletonFileName, CA_CharEditModel);
        Serialization::SContext<IDefaultSkeleton> contextSkeleton(ar, skeleton);

        ar(skeletonParameters.includes, "includes", "+Includes");
        if (ar.IsEdit() && ar.IsOutput())
        {
            System* system = ar.FindContext<System>();
            UpdateIncludedAnimationSet(system->skeletonList.get());
            ar(includedAnimationSetFilter, "includedAnimationSetFilter", "+!Included Animation Set Filter");
        }
        ar(skeletonParameters.animationSetFilter, "animationSetFilter", "+[+]Animation Set Filter");
        ar(ResourceFilePath(skeletonParameters.animationEventDatabase, "Animation Events"), "animationEventDatabase", "<Events");

        ar(ResourceFolderPath(skeletonParameters.dbaPath, "Animations"), "dbaPath", "<DBA Path");
        ar.Doc("Folder path for DBA files. All DBA files from this folder will be included.");
        
        ar(skeletonParameters.individualDBAs, "individualDBAs", "Individual DBAs");

        ar(skeletonParameters.bboxExtension, "bboxExtension", "-Bounding Box Extension");
        ar(skeletonParameters.bboxIncludes, "boundingBoxInclude", "-Bounding Box Include");

        ar(skeletonParameters.jointLods, "lods", "Joint LOD");

        auto& ikDefinition = skeletonParameters.ikDefinition;
        ar(ikDefinition, "ikDefinition", ikDefinition.HasEnabledDefinitions() ? "IK Definition" : "-IK Definition");
    }

    void SkeletonContent::GetDependencies(vector<string>* deps) const
    {
        for (size_t i = 0; i < skeletonParameters.includes.size(); ++i)
        {
            deps->push_back(skeletonParameters.includes[i].filename);
        }
    }

    static bool ExpandIncludes(AnimationSetFilter* outFilter, const std::vector<string>& includeStack, const vector<SkeletonParametersInclude>& includes, const string& selfPath, ExplorerFileList* skeletonList, string& errorListString)
    {
        bool result = true;
        std::vector<string> stack = includeStack;
        std::vector<AnimationFilterFolder> includedFolders;
        for (size_t i = 0; i < includes.size(); ++i)
        {
            const string& filename = includes[i].filename;
            string currentIncludeContext = selfPath;
            if (!stack.empty())
            {
                currentIncludeContext = stack.back().c_str();
            }
            
            if (filename == selfPath || stl::find(stack, filename))
            {
                AZStd::string errorString = AZStd::string::format("Recursive inclusion of CHRPARAMS: '%s' while processing '%s'", includes[i].filename.c_str(), currentIncludeContext.c_str());

                if (errorListString.length() > 0)
                {
                    errorListString = errorListString + ", ";
                }
                errorListString = errorListString + errorString.c_str();
                result = false;
                continue;
            }

            SEntry<SkeletonContent>* entry = skeletonList->GetEntryByPath<SkeletonContent>(filename.c_str());
            if (entry)
            {
                skeletonList->LoadOrGetChangedEntry(entry->id);

                includedFolders.insert(includedFolders.end(),
                    entry->content.skeletonParameters.animationSetFilter.folders.begin(),
                    entry->content.skeletonParameters.animationSetFilter.folders.end());

                AnimationSetFilter filter;
                stack.push_back(filename);
                if (!ExpandIncludes(&filter, stack, entry->content.skeletonParameters.includes, selfPath, skeletonList, errorListString))
                {
                    result = false;
                }
                stack.pop_back();
                includedFolders.insert(includedFolders.end(),
                    filter.folders.begin(), filter.folders.end());
            }
        }

        outFilter->folders.insert(outFilter->folders.begin(),
            includedFolders.begin(), includedFolders.end());
        return result;
    }

    void SkeletonContent::UpdateIncludedAnimationSet(ExplorerFileList* skeletonList)
    {
        includedAnimationSetFilter = AnimationSetFilter();
        const string selfPath = PathUtil::ReplaceExtension(skeletonParameters.skeletonFileName, ".chrparams");
        string errorListString;
        m_filterInValidState = ExpandIncludes(&includedAnimationSetFilter, std::vector<string>(), skeletonParameters.includes, selfPath, skeletonList, errorListString);
        m_errorListString = errorListString;
    }

    bool SkeletonContent::ComposeCompleteAnimationSetFilter(AnimationSetFilter* outFilter, ExplorerFileList* skeletonList) const
    {
        *outFilter = skeletonParameters.animationSetFilter;
        const string selfPath = PathUtil::ReplaceExtension(skeletonParameters.skeletonFileName, ".chrparams");
        string errorListString;
        if (!ExpandIncludes(&*outFilter, vector<string>(), skeletonParameters.includes, selfPath, skeletonList, errorListString))
        { 
            CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_ERROR, "%s", errorListString.c_str());
            return false;
        }
        return true;
    }
}