/*****************************************************************************
 * Copyright (c) 2014-2026 OpenRCT2 developers
 *
 * For a complete list of all authors, please refer to contributors.md
 * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
 *
 * OpenRCT2 is licensed under the GNU General Public License version 3.
 *****************************************************************************/

#include "ObjectList.h"

#include "../Context.h"
#include "../Game.h"
#include "../core/EnumUtils.hpp"
#include "../object/Object.h"
#include "../sawyer_coding/SawyerCoding.h"
#include "ObjectLimits.h"
#include "ObjectManager.h"
#include "ObjectRepository.h"

#include <array>
#include <cstring>

namespace OpenRCT2
{
    // 0x0098DA00
    static constexpr std::array<int32_t, EnumValue(ObjectType::count)> kObjectEntryGroupCounts = {
        kMaxRideObjects,         // rides
        kMaxSmallSceneryObjects, // small scenery
        kMaxLargeSceneryObjects, // large scenery
        kMaxWallSceneryObjects,  // walls
        kMaxBannerObjects,       // banners
        kMaxPathObjects,         // paths
        kMaxPathAdditionObjects, // path additions
        kMaxSceneryGroupObjects, // scenery sets
        kMaxParkEntranceObjects, // park entrance
        kMaxWaterObjects,        // water
        kMaxScenarioMetaObjects, // scenario meta
        kMaxTerrainSurfaceObjects, kMaxTerrainEdgeObjects,     kMaxStationObjects,
        kMaxMusicObjects,          kMaxFootpathSurfaceObjects, kMaxFootpathRailingsObjects,
        kMaxAudioObjects,          kMaxPeepNamesObjects,       kMaxPeepAnimationsObjects,
        kMaxClimateObjects,
    };
    static_assert(std::size(kObjectEntryGroupCounts) == EnumValue(ObjectType::count));

    size_t getObjectEntryGroupCount(ObjectType objectType)
    {
        return kObjectEntryGroupCounts[EnumValue(objectType)];
    }

    size_t getObjectTypeLimit(ObjectType type)
    {
        auto index = EnumValue(type);
        if (index >= EnumValue(ObjectType::count))
            return 0;
        return static_cast<size_t>(kObjectEntryGroupCounts[index]);
    }

    ObjectList::const_iterator::const_iterator(const ObjectList* parent, bool end)
    {
        _parent = parent;
        _subList = _parent->_subLists.size();
        _index = 0;
    }

    void ObjectList::const_iterator::MoveToNextEntry()
    {
        do
        {
            if (_subList < _parent->_subLists.size())
            {
                auto subListSize = _parent->_subLists[_subList].size();
                if (_index < subListSize)
                {
                    _index++;
                    if (_index == subListSize)
                    {
                        _subList++;
                        _index = 0;
                    }
                }
            }
            else
            {
                break;
            }
        } while (!_parent->_subLists[_subList][_index].HasValue());
    }

    ObjectList::const_iterator& ObjectList::const_iterator::operator++()
    {
        MoveToNextEntry();
        return *this;
    }

    ObjectList::const_iterator ObjectList::const_iterator::operator++(int)
    {
        return *this;
    }

    const ObjectEntryDescriptor& ObjectList::const_iterator::operator*()
    {
        return _parent->_subLists[_subList][_index];
    }

    bool ObjectList::const_iterator::operator==(const_iterator& rhs)
    {
        return _parent == rhs._parent && _subList == rhs._subList && _index == rhs._index;
    }

    bool ObjectList::const_iterator::operator!=(const_iterator& rhs)
    {
        return !(*this == rhs);
    }

    ObjectList::const_iterator ObjectList::begin() const
    {
        return const_iterator(this, false);
    }

    ObjectList::const_iterator ObjectList::end() const
    {
        return const_iterator(this, true);
    }

    std::vector<ObjectEntryDescriptor>& ObjectList::GetList(ObjectType type)
    {
        auto index = static_cast<size_t>(type);
        while (_subLists.size() <= index)
        {
            _subLists.resize(static_cast<size_t>(index) + 1);
        }
        return _subLists[index];
    }

    std::vector<ObjectEntryDescriptor>& ObjectList::GetList(ObjectType type) const
    {
        return const_cast<ObjectList*>(this)->GetList(type);
    }

    const ObjectEntryDescriptor& ObjectList::GetObject(ObjectType type, ObjectEntryIndex index) const
    {
        const auto& subList = GetList(type);
        if (subList.size() > index)
        {
            return subList[index];
        }

        static ObjectEntryDescriptor placeholder;
        return placeholder;
    }

    ObjectEntryIndex ObjectList::Add(const ObjectEntryDescriptor& entry)
    {
        auto& subList = GetList(entry.GetType());
        auto index = subList.size();
        subList.push_back(entry);
        return static_cast<ObjectEntryIndex>(index);
    }

    void ObjectList::SetObject(ObjectEntryIndex index, const ObjectEntryDescriptor& entry)
    {
        auto& subList = GetList(entry.GetType());
        if (subList.size() <= index)
        {
            subList.resize(static_cast<size_t>(index) + 1);
        }
        subList[index] = entry;
    }

    void ObjectList::SetObject(ObjectType type, ObjectEntryIndex index, std::string_view identifier)
    {
        auto entry = ObjectEntryDescriptor(identifier);
        entry.Type = type;
        SetObject(index, entry);
    }

    ObjectEntryIndex ObjectList::Find(ObjectType type, std::string_view identifier) const
    {
        auto& subList = GetList(type);
        for (size_t i = 0; i < subList.size(); i++)
        {
            if (subList[i].Generation == ObjectGeneration::JSON && subList[i].Identifier == identifier)
            {
                return static_cast<ObjectEntryIndex>(i);
            }
        }
        return kObjectEntryIndexNull;
    }

    // Intended to be used to find non-custom legacy objects. For internal use only.
    ObjectEntryIndex ObjectList::FindLegacy(ObjectType type, std::string_view identifier) const
    {
        auto& subList = GetList(type);
        for (size_t i = 0; i < subList.size(); i++)
        {
            if (subList[i].Generation == ObjectGeneration::DAT && subList[i].Entry.GetName() == identifier
                && subList[i].Entry.GetSourceGame() != ObjectSourceGame::custom)
            {
                return static_cast<ObjectEntryIndex>(i);
            }
        }
        return kObjectEntryIndexNull;
    }

    /**
     *
     *  rct2: 0x006AB344
     */
    void ObjectCreateIdentifierName(char* string_buffer, size_t size, const RCTObjectEntry* object)
    {
        snprintf(string_buffer, size, "%.8s/%4X%4X", object->name, object->flags, object->checksum);
    }

    void ObjectGetTypeEntryIndex(size_t index, ObjectType* outObjectType, ObjectEntryIndex* outEntryIndex)
    {
        uint8_t objectType = EnumValue(ObjectType::ride);
        for (size_t groupCount : kObjectEntryGroupCounts)
        {
            if (index >= groupCount)
            {
                index -= groupCount;
                objectType++;
            }
            else
            {
                break;
            }
        }

        if (outObjectType != nullptr)
            *outObjectType = static_cast<ObjectType>(objectType);
        if (outEntryIndex != nullptr)
            *outEntryIndex = static_cast<ObjectEntryIndex>(index);
    }

    void ObjectEntryGetNameFixed(utf8* buffer, size_t bufferSize, const RCTObjectEntry* entry)
    {
        bufferSize = std::min(static_cast<size_t>(kDatNameLength) + 1, bufferSize);
        std::memcpy(buffer, entry->name, bufferSize - 1);
        buffer[bufferSize - 1] = 0;
    }

    void* ObjectEntryGetChunk(ObjectType objectType, ObjectEntryIndex index)
    {
        auto& objectMgr = GetContext()->GetObjectManager();
        auto* object = objectMgr.GetLoadedObject(objectType, index);
        if (object != nullptr)
        {
            return object->GetLegacyData();
        }
        return nullptr;
    }

    const Object* ObjectEntryGetObject(ObjectType objectType, ObjectEntryIndex index)
    {
        auto& objectMgr = GetContext()->GetObjectManager();
        return objectMgr.GetLoadedObject(objectType, index);
    }
} // namespace OpenRCT2
