#include "Litter.h"

#include "../Cheats.h"
#include "../GameState.h"
#include "../core/DataSerialiser.h"
#include "../localisation/StringIds.h"
#include "../world/Footpath.h"
#include "../world/Map.h"
#include "../world/tile_element/PathElement.h"
#include "EntityList.h"
#include "EntityRegistry.h"

#include <sfl/small_vector.hpp>

namespace OpenRCT2
{
    template<>
    bool EntityBase::is<Litter>() const
    {
        return type == EntityType::litter;
    }

    static bool IsLocationLitterable(const CoordsXYZ& mapPos)
    {
        if (!MapIsLocationOwned(mapPos))
            return false;

        TileElement* tileElement = MapGetFirstElementAt(mapPos);
        if (tileElement == nullptr)
            return false;
        do
        {
            if (tileElement->getType() != TileElementType::path)
                continue;

            int32_t pathBaseZ = tileElement->getBaseZ();
            int32_t pathTopZ = pathBaseZ + (tileElement->asPath()->isSloped() ? kPathHeightStep : 0);
            if (!(pathBaseZ <= mapPos.z && pathTopZ >= mapPos.z))
                continue;

            return !tileElementIsUnderground(tileElement);
        } while (!(tileElement++)->isLastForTile());
        return false;
    }

    /**
     *
     *  rct2: 0x0067375D
     */
    void Litter::create(const CoordsXYZD& litterPos, Type type)
    {
        auto& gameState = getGameState();
        if (gameState.cheats.disableLittering)
            return;

        auto offsetLitterPos = litterPos
            + CoordsXY{ CoordsDirectionDelta[litterPos.direction >> 3].x / 8,
                        CoordsDirectionDelta[litterPos.direction >> 3].y / 8 };

        if (!IsLocationLitterable(offsetLitterPos))
            return;

        if (gameState.entities.getEntityListCount(EntityType::litter) >= 500)
        {
            Litter* newestLitter = nullptr;
            uint32_t newestLitterCreationTick = 0;
            for (auto litter : EntityList<Litter>())
            {
                if (newestLitterCreationTick <= litter->creationTick)
                {
                    newestLitterCreationTick = litter->creationTick;
                    newestLitter = litter;
                }
            }

            if (newestLitter != nullptr)
            {
                newestLitter->invalidate();
                gameState.entities.entityRemove(newestLitter);
            }
        }

        Litter* litter = gameState.entities.createEntity<Litter>();
        if (litter == nullptr)
            return;

        litter->orientation = offsetLitterPos.direction;
        litter->spriteData.width = 6;
        litter->spriteData.heightMin = 6;
        litter->spriteData.heightMax = 3;
        litter->subType = type;
        litter->moveTo(offsetLitterPos);
        litter->creationTick = gameState.currentTicks;
    }

    /**
     *
     *  rct2: 0x006738E1
     */
    void Litter::removeAt(const CoordsXYZ& litterPos)
    {
        // There can be a lot of litter entities on the same tile, avoid heap allocations
        // by having the first 512 stored in a small_vector which is on the stack.
        sfl::small_vector<Litter*, 512> removals;
        for (auto litter : EntityTileList<Litter>(litterPos))
        {
            if (abs(litter->z - litterPos.z) <= 16)
            {
                if (abs(litter->x - litterPos.x) <= 8 && abs(litter->y - litterPos.y) <= 8)
                {
                    removals.push_back(litter);
                }
            }
        }
        for (auto* litter : removals)
        {
            litter->invalidate();
            getGameState().entities.entityRemove(litter);
        }
    }

    static const StringId litterNames[12] = {
        STR_LITTER_VOMIT,
        STR_LITTER_VOMIT,
        STR_SHOP_ITEM_SINGULAR_EMPTY_CAN,
        STR_SHOP_ITEM_SINGULAR_RUBBISH,
        STR_SHOP_ITEM_SINGULAR_EMPTY_BURGER_BOX,
        STR_SHOP_ITEM_SINGULAR_EMPTY_CUP,
        STR_SHOP_ITEM_SINGULAR_EMPTY_BOX,
        STR_SHOP_ITEM_SINGULAR_EMPTY_BOTTLE,
        STR_SHOP_ITEM_SINGULAR_EMPTY_BOWL_RED,
        STR_SHOP_ITEM_SINGULAR_EMPTY_DRINK_CARTON,
        STR_SHOP_ITEM_SINGULAR_EMPTY_JUICE_CUP,
        STR_SHOP_ITEM_SINGULAR_EMPTY_BOWL_BLUE,
    };

    StringId Litter::getName() const
    {
        if (EnumValue(subType) >= std::size(litterNames))
            return kStringIdNone;
        return litterNames[EnumValue(subType)];
    }

    uint32_t Litter::getAge() const
    {
        return getGameState().currentTicks - creationTick;
    }

    void Litter::serialise(DataSerialiser& stream)
    {
        EntityBase::serialise(stream);

        stream << subType;
        stream << creationTick;
    }
} // namespace OpenRCT2
