/*
 Copyright (C) 2010-2017 Kristian Duske

 This file is part of TrenchBroom.

 TrenchBroom is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 TrenchBroom is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with TrenchBroom. If not, see <http://www.gnu.org/licenses/>.
 */

#include "EntityDefinitionManager.h"

#include "CollectionUtils.h"
#include "StringUtils.h"
#include "IO/EntityDefinitionLoader.h"
#include "Model/AttributableNode.h"
#include "Model/EntityAttributes.h"

#include <cassert>

namespace TrenchBroom {
    namespace Assets {
        EntityDefinitionManager::~EntityDefinitionManager() {
            clear();
        }

        void EntityDefinitionManager::loadDefinitions(const IO::Path& path, const IO::EntityDefinitionLoader& loader, IO::ParserStatus& status) {
            setDefinitions(loader.loadEntityDefinitions(status, path));
        }

        void EntityDefinitionManager::setDefinitions(const EntityDefinitionList& newDefinitions) {
            clear();

            m_definitions = newDefinitions;

            updateIndices();
            updateGroups();
            updateCache();
            bindObservers();
        }

        void EntityDefinitionManager::clear() {
            clearCache();
            clearGroups();
            VectorUtils::clearAndDelete(m_definitions);
        }

        EntityDefinition* EntityDefinitionManager::definition(const Model::AttributableNode* attributable) const {
            ensure(attributable != nullptr, "attributable is null");
            return definition(attributable->attribute(Model::AttributeNames::Classname));
        }

        EntityDefinition* EntityDefinitionManager::definition(const Model::AttributeValue& classname) const {
            auto it = m_cache.find(classname);
            if (it == std::end(m_cache))
                return nullptr;
            return it->second;
        }

        EntityDefinitionList EntityDefinitionManager::definitions(const EntityDefinition::Type type, const EntityDefinition::SortOrder order) const {
            return EntityDefinition::filterAndSort(m_definitions, type, order);
        }

        const EntityDefinitionList& EntityDefinitionManager::definitions() const {
            return m_definitions;
        }

        const EntityDefinitionGroup::List& EntityDefinitionManager::groups() const {
            return m_groups;
        }

        void EntityDefinitionManager::updateIndices() {
            for (size_t i = 0; i < m_definitions.size(); ++i)
                m_definitions[i]->setIndex(i+1);
        }

        void EntityDefinitionManager::updateGroups() {
            clearGroups();

            using GroupMap = std::map<String, EntityDefinitionList>;
            GroupMap groupMap;

            for (size_t i = 0; i < m_definitions.size(); ++i) {
                EntityDefinition* definition = m_definitions[i];
                const String groupName = definition->groupName();
                groupMap[groupName].push_back(definition);
            }

            for (const auto& entry : groupMap) {
                const String& groupName = entry.first;
                const EntityDefinitionList& definitions = entry.second;
                m_groups.push_back(EntityDefinitionGroup(groupName, definitions));
            }
        }

        void EntityDefinitionManager::updateCache() {
            clearCache();
            for (EntityDefinition* definition : m_definitions)
                m_cache[definition->name()] = definition;
        }

        void EntityDefinitionManager::bindObservers() {
            for (EntityDefinition* definition : m_definitions)
                definition->usageCountDidChangeNotifier.addObserver(usageCountDidChangeNotifier);
        }

        void EntityDefinitionManager::clearCache() {
            m_cache.clear();
        }

        void EntityDefinitionManager::clearGroups() {
            m_groups.clear();
        }
    }
}
