/*
* 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.

// CEntryList is used to store content of setup files in memory.
//
// Its responsibility:
//    Assign unique int identifiers;
//    Track if entry are modified since last save and last change;
//    Index entries by id (int), path and unique name, in some cases;

#pragma once

#include <map>
#include <memory>
#include <vector>
#include "Strings.h"

namespace Serialization
{
    class IArchive;
};

namespace CharacterTool
{
    using std::map;
    using std::vector;
    using std::unique_ptr;

    typedef unsigned int EntryId;

    struct EntryBase
    {
        EntryId id;
        string name;
        string path;
        bool loaded;
        bool modified;
        bool dataLostDuringLoading;
        bool failedToLoad;
        bool pendingSave;

        vector<char> lastContent;
        vector<char> savedContent;

        EntryBase()
            : id()
            , loaded(false)
            , modified(false)
            , dataLostDuringLoading(false)
            , failedToLoad(false)
            , pendingSave(false)
        {
        }

        virtual ~EntryBase() {}
        virtual void Serialize(Serialization::IArchive& ar) = 0;
        virtual void Reset() = 0;
        void StoreSavedContent();
    };

    typedef vector<unique_ptr<EntryBase> > SEntries;

    class EntryListBase
    {
    public:
        EntryListBase()
            : m_nextId(1)
            , m_idProvider(0) {}

        virtual ~EntryListBase() = default;

        size_t Count() const{ return m_entries.size(); }
        void Clear();
        void SaveAll();
        bool RemoveById(EntryId id);
        unsigned int RemoveByPath(const char* path);

        void SetIdProvider(EntryListBase* list) { m_idProvider = list; }

        virtual EntryBase* AddEntry(bool* isNewEntry, const char* path, const char* name, bool resetIfExists) = 0;

        bool EntryChanged(vector<char>* previousContent, EntryBase* entry) const;
        bool EntryReverted(vector<char>* previousContent, EntryBase* entry) const;
        bool EntryAdded(EntryBase* entry) const;
        bool EntrySaved(EntryBase* entry) const;

        EntryBase* GetBaseById(EntryId id) const;
        EntryBase* GetBaseByName(const char* name) const;
        EntryBase* GetBaseByPath(const char* path) const;
        EntryBase* GetBaseByIndex(size_t index) const;
    protected:
        void AddEntry(EntryBase* entry);
        EntryId MakeNextId();
        EntryListBase* m_idProvider;

        typedef map<string, EntryBase*, less_stricmp<string> > EntriesByPath;
        EntriesByPath m_entriesByPath;
        EntriesByPath m_entriesByName;
        typedef map<int, EntryBase*> EntriesById;
        EntriesById m_entriesById;
        SEntries m_entries;
        EntryId m_nextId;
    };


    template<class T>
    struct SEntry
        : public EntryBase
    {
        T content;

        void Serialize(Serialization::IArchive& ar) override;
        void Reset() override
        {
            content.Reset();
        }
    };

    template<class T>
    class CEntryList
        : public EntryListBase
    {
    public:
        SEntry<T>* AddEntry(bool* isNewEntry, const char* path, const char* name, bool resetIfExists) override
        {
            SEntry<T>* entry = GetByPath(path);
            if (!entry)
            {
                if (isNewEntry)
                {
                    *isNewEntry = true;
                }
                entry = new SEntry<T>;
                entry->path = path;
                if (name)
                {
                    entry->name = name;
                }
                EntryListBase::AddEntry(entry);
            }
            else if (isNewEntry)
            {
                if (resetIfExists)
                {
                    entry->Reset();
                }
                *isNewEntry = false;
            }
            return entry;
        }

        bool EntryReverted(vector<char>* previousContent, SEntry<T>* entry) const { return EntryListBase::EntryReverted(previousContent, entry);    }
        bool EntryChanged(vector<char>* previousContent, SEntry<T>* entry) const { return EntryListBase::EntryChanged(previousContent, entry); }
        bool EntrySaved(SEntry<T>* entry)   const { return EntryListBase::EntrySaved(entry); }
        SEntry<T>* GetById(EntryId id) const { return static_cast<SEntry<T>*>(GetBaseById(id)); }
        SEntry<T>* GetByIndex(size_t index) const { return static_cast<SEntry<T>*>(GetBaseByIndex(index));  }
        SEntry<T>* GetByName(const char* name) const { return static_cast<SEntry<T>*>(GetBaseByName(name)); }
        SEntry<T>* GetByPath(const char* path) const { return static_cast<SEntry<T>*>(GetBaseByPath(path)); }
    };
}