/*****************************************************************************
 * Copyright (c) 2014-2025 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.
 *****************************************************************************/

#pragma once

#include "../Identifiers.h"
#include "Location.hpp"

#include <array>
#include <initializer_list>
#include <optional>
#include <vector>

constexpr uint8_t kMinimumLandHeight = 2;
constexpr uint8_t kMaximumLandHeight = 254;
constexpr uint8_t kMinimumWaterHeight = 2;
constexpr uint8_t kMaximumWaterHeight = 254;
/**
 * The land height that counts as 0 metres/feet for the land height labels and altitude graphs.
 */
constexpr uint8_t kMapBaseZ = 7;

constexpr uint8_t kMinimumMapSizeTechnical = 5;
constexpr uint16_t kMaximumMapSizeTechnical = 1001;
constexpr int16_t kMinimumMapSizePractical = (kMinimumMapSizeTechnical - 2);
constexpr int16_t kMaximumMapSizePractical = (kMaximumMapSizeTechnical - 2);
constexpr const int32_t kMaximumMapSizeBig = kCoordsXYStep * kMaximumMapSizeTechnical;
constexpr int32_t kMaximumTileStartXY = kMaximumMapSizeBig - kCoordsXYStep;
constexpr const int32_t kLandHeightStep = 2 * kCoordsZStep;
constexpr const int32_t kWaterHeightStep = 2 * kCoordsZStep;
constexpr const int32_t kMinimumLandZ = kMinimumLandHeight * kCoordsZStep;
constexpr TileCoordsXY kDefaultMapSize = { 150, 150 };
// How high construction has to be off the ground when the player owns construction rights, in tile coords.
constexpr uint8_t kConstructionRightsClearanceSmall = 3;
// Same as previous, but in big coords.
constexpr const uint8_t kConstructionRightsClearanceBig = 3 * kCoordsZStep;

constexpr int16_t kMapMinimumXY = (-kMaximumMapSizeTechnical);

constexpr uint32_t kMaxTileElementsWithSpaceRoom = 0x1000000;
constexpr uint32_t kMaxTileElements = kMaxTileElementsWithSpaceRoom - 512;

using PeepSpawn = CoordsXYZD;

namespace OpenRCT2
{
    struct BannerElement;
    struct EntranceElement;
    struct LargeSceneryElement;
    struct PathElement;
    struct SmallSceneryElement;
    struct SurfaceElement;
    struct TileElement;
    struct TrackElement;
    struct WallElement;

    enum class TileElementType : uint8_t;
    enum class TrackElemType : uint16_t;
} // namespace OpenRCT2

struct CoordsXYE : public CoordsXY
{
    CoordsXYE() = default;
    constexpr CoordsXYE(int32_t _x, int32_t _y, OpenRCT2::TileElement* _e)
        : CoordsXY(_x, _y)
        , element(_e)
    {
    }

    constexpr CoordsXYE(const CoordsXY& c, OpenRCT2::TileElement* _e)
        : CoordsXY(c)
        , element(_e)
    {
    }
    OpenRCT2::TileElement* element = nullptr;
};

enum
{
    MAP_SELECT_FLAG_ENABLE = 1 << 0,
    MAP_SELECT_FLAG_ENABLE_CONSTRUCT = 1 << 1,
    MAP_SELECT_FLAG_ENABLE_ARROW = 1 << 2,
    MAP_SELECT_FLAG_GREEN = 1 << 3,
};

enum
{
    MAP_SELECT_TYPE_CORNER_0,
    MAP_SELECT_TYPE_CORNER_1,
    MAP_SELECT_TYPE_CORNER_2,
    MAP_SELECT_TYPE_CORNER_3,
    MAP_SELECT_TYPE_FULL,
    MAP_SELECT_TYPE_FULL_WATER,
    MAP_SELECT_TYPE_FULL_LAND_RIGHTS,
    MAP_SELECT_TYPE_QUARTER_0,
    MAP_SELECT_TYPE_QUARTER_1,
    MAP_SELECT_TYPE_QUARTER_2,
    MAP_SELECT_TYPE_QUARTER_3,
    MAP_SELECT_TYPE_EDGE_0,
    MAP_SELECT_TYPE_EDGE_1,
    MAP_SELECT_TYPE_EDGE_2,
    MAP_SELECT_TYPE_EDGE_3,
};

extern const std::array<CoordsXY, 8> CoordsDirectionDelta;
extern const TileCoordsXY TileDirectionDelta[];

CoordsXY GetMapSizeUnits();
CoordsXY GetMapSizeMinus2();
CoordsXY GetMapSizeMaxXY();

extern uint16_t gMapSelectFlags;
extern uint16_t gMapSelectType;
extern CoordsXY gMapSelectPositionA;
extern CoordsXY gMapSelectPositionB;
extern CoordsXYZ gMapSelectArrowPosition;
extern uint8_t gMapSelectArrowDirection;

extern std::vector<CoordsXY> gMapSelectionTiles;

extern uint32_t gLandRemainingOwnershipSales;
extern uint32_t gLandRemainingConstructionSales;

extern bool gMapLandRightsUpdateSuccess;

namespace OpenRCT2
{
    struct GameState_t;
}

void ReorganiseTileElements();
const std::vector<OpenRCT2::TileElement>& GetTileElements();
void SetTileElements(OpenRCT2::GameState_t& gameState, std::vector<OpenRCT2::TileElement>&& tileElements);
void StashMap();
void UnstashMap();
std::vector<OpenRCT2::TileElement> GetReorganisedTileElementsWithoutGhosts();

void MapInit(const TileCoordsXY& size);

void MapCountRemainingLandRights();
void MapStripGhostFlagFromElements();
OpenRCT2::TileElement* MapGetFirstElementAt(const CoordsXY& tilePos);
OpenRCT2::TileElement* MapGetFirstElementAt(const TileCoordsXY& tilePos);
OpenRCT2::TileElement* MapGetNthElementAt(const CoordsXY& coords, int32_t n);
OpenRCT2::TileElement* MapGetFirstTileElementWithBaseHeightBetween(
    const TileCoordsXYRangedZ& loc, OpenRCT2::TileElementType type);
void MapSetTileElement(const TileCoordsXY& tilePos, OpenRCT2::TileElement* elements);
int32_t MapHeightFromSlope(const CoordsXY& coords, int32_t slopeDirection, bool isSloped);
OpenRCT2::BannerElement* MapGetBannerElementAt(const CoordsXYZ& bannerPos, uint8_t direction);
OpenRCT2::SurfaceElement* MapGetSurfaceElementAt(const TileCoordsXY& coords);
OpenRCT2::SurfaceElement* MapGetSurfaceElementAt(const CoordsXY& coords);
OpenRCT2::PathElement* MapGetPathElementAt(const TileCoordsXYZ& loc);
OpenRCT2::WallElement* MapGetWallElementAt(const CoordsXYZD& wallCoords);
OpenRCT2::WallElement* MapGetWallElementAt(const CoordsXYRangedZ& coords);
OpenRCT2::SmallSceneryElement* MapGetSmallSceneryElementAt(const CoordsXYZ& sceneryCoords, int32_t type, uint8_t quadrant);
OpenRCT2::EntranceElement* MapGetParkEntranceElementAt(const CoordsXYZ& entranceCoords, bool ghost);
OpenRCT2::EntranceElement* MapGetRideEntranceElementAt(const CoordsXYZ& entranceCoords, bool ghost);
OpenRCT2::EntranceElement* MapGetRideExitElementAt(const CoordsXYZ& exitCoords, bool ghost);
uint8_t MapGetHighestLandHeight(const MapRange& range);
uint8_t MapGetLowestLandHeight(const MapRange& range);
bool MapCoordIsConnected(const TileCoordsXYZ& loc, uint8_t faceDirection);
void MapUpdatePathWideFlags();
bool MapIsLocationValid(const CoordsXY& coords);
bool MapIsEdge(const CoordsXY& coords);
bool MapCanBuildAt(const CoordsXYZ& loc);
bool MapIsLocationOwned(const CoordsXYZ& loc);
bool MapIsLocationInPark(const CoordsXY& coords);
bool MapIsLocationOwnedOrHasRights(const CoordsXY& loc);
bool MapSurfaceIsBlocked(const CoordsXY& mapCoords);
void MapRemoveAllRides();
void MapInvalidateMapSelectionTiles();
void MapInvalidateSelectionRect();
bool MapCheckCapacityAndReorganise(const CoordsXY& loc, size_t numElements = 1);
int16_t TileElementHeight(const CoordsXY& loc);
int16_t TileElementHeight(const CoordsXYZ& loc, uint8_t slope);
int16_t TileElementWaterHeight(const CoordsXY& loc);
void TileElementRemove(OpenRCT2::TileElement* tileElement);
OpenRCT2::TileElement* TileElementInsert(const CoordsXYZ& loc, int32_t occupiedQuadrants, OpenRCT2::TileElementType type);

template<typename T = OpenRCT2::TileElement>
T* MapGetFirstTileElementWithBaseHeightBetween(const TileCoordsXYRangedZ& loc)
{
    auto* element = MapGetFirstTileElementWithBaseHeightBetween(loc, T::kElementType);
    return element != nullptr ? element->template as<T>() : nullptr;
}

template<typename T>
T* TileElementInsert(const CoordsXYZ& loc, int32_t occupiedQuadrants)
{
    auto* element = TileElementInsert(loc, occupiedQuadrants, T::kElementType);
    return (element != nullptr) ? element->template as<T>() : nullptr;
}

struct TileElementIterator
{
    int32_t x;
    int32_t y;
    OpenRCT2::TileElement* element;
};
#ifdef PLATFORM_32BIT
static_assert(sizeof(TileElementIterator) == 12);
#endif

void TileElementIteratorBegin(TileElementIterator* it);
int32_t TileElementIteratorNext(TileElementIterator* it);
void TileElementIteratorRestartForTile(TileElementIterator* it);

void MapUpdateTiles();
int32_t MapGetHighestZ(const CoordsXY& loc);

bool TileElementWantsPathConnectionTowards(const TileCoordsXYZD& coords, const OpenRCT2::TileElement* const elementToBeRemoved);

void MapRemoveOutOfRangeElements();
void MapExtendBoundarySurfaceX();
void MapExtendBoundarySurfaceY();

bool MapLargeScenerySignSetColour(const CoordsXYZD& signPos, int32_t sequence, uint8_t mainColour, uint8_t textColour);

void MapInvalidateTile(const CoordsXYRangedZ& tilePos);
void MapInvalidateTileZoom1(const CoordsXYRangedZ& tilePos);
void MapInvalidateTileZoom0(const CoordsXYRangedZ& tilePos);
void MapInvalidateTileFull(const CoordsXY& tilePos);
void MapInvalidateElement(const CoordsXY& elementPos, OpenRCT2::TileElement* tileElement);
void MapInvalidateRegion(const CoordsXY& mins, const CoordsXY& maxs);

int32_t MapGetTileSide(const CoordsXY& mapPos);
int32_t MapGetTileQuadrant(const CoordsXY& mapPos);
int32_t MapGetCornerHeight(int32_t z, int32_t slope, int32_t direction);
int32_t TileElementGetCornerHeight(const OpenRCT2::SurfaceElement* surfaceElement, int32_t direction);

void MapClearAllElements();
void ClearElementAt(const CoordsXY& loc, OpenRCT2::TileElement** elementPtr);

OpenRCT2::LargeSceneryElement* MapGetLargeScenerySegment(const CoordsXYZD& sceneryPos, int32_t sequence);
std::optional<CoordsXYZ> MapLargeSceneryGetOrigin(
    const CoordsXYZD& sceneryPos, int32_t sequence, OpenRCT2::LargeSceneryElement** outElement);

OpenRCT2::TrackElement* MapGetTrackElementAt(const CoordsXYZ& trackPos);
OpenRCT2::TileElement* MapGetTrackElementAtOfType(const CoordsXYZ& trackPos, OpenRCT2::TrackElemType trackType);
OpenRCT2::TileElement* MapGetTrackElementAtOfTypeSeq(
    const CoordsXYZ& trackPos, OpenRCT2::TrackElemType trackType, int32_t sequence);
OpenRCT2::TrackElement* MapGetTrackElementAtOfType(const CoordsXYZD& location, OpenRCT2::TrackElemType trackType);
OpenRCT2::TrackElement* MapGetTrackElementAtOfTypeSeq(
    const CoordsXYZD& location, OpenRCT2::TrackElemType trackType, int32_t sequence);
OpenRCT2::TileElement* MapGetTrackElementAtOfTypeFromRide(
    const CoordsXYZ& trackPos, OpenRCT2::TrackElemType trackType, RideId rideIndex);
OpenRCT2::TileElement* MapGetTrackElementAtFromRide(const CoordsXYZ& trackPos, RideId rideIndex);
OpenRCT2::TileElement* MapGetTrackElementAtWithDirectionFromRide(const CoordsXYZD& trackPos, RideId rideIndex);
OpenRCT2::TileElement* MapGetTrackElementAtBeforeSurfaceFromRide(const CoordsXYZ& trackPos, RideId rideIndex);

bool MapIsLocationAtEdge(const CoordsXY& loc);

uint16_t CheckMaxAllowableLandRightsForTile(const CoordsXYZ& tileMapPos);

void FixLandOwnershipTilesWithOwnership(std::vector<TileCoordsXY> tiles, uint8_t ownership);
MapRange ClampRangeWithinMap(const MapRange& range);
void ShiftMap(const TileCoordsXY& amount);
