#ifndef REMOTE_MAP_HPP_
#define REMOTE_MAP_HPP_

#include <Controller/Controller.hpp>
#include <Serialize/Utils.hpp>
#include <World/LocalWorld/LocalWorld.hpp>
#include <Item/IEquipment.hpp>
#include <Serialize/Serialize.hpp>
#include <array>
#include <cstdint>
#include <functional>
#include <unordered_map>

/**
 * @brief Remote calls for controlling a map.
 */
class RemoteMap final
{
public:
    struct Cell
    {
        uint32_t blockId;
        cint32 z;
    };

    struct Tile
    {
        std::array<Cell, 16 * 16> cells = {};
    };

    using TileUpdatedCallback = std::function<void(wvec2, const Tile&, cint32)>;

private:
    static constexpr int rangeZ = 10;

    struct MapTileHandle
    {
        std::vector<ChunkHandle> chunks;
        wvec2 base;
        cint32 startZ, endZ;
        LocalWorld &world;
        IEquipment::MapLevel mapLevel;

        MapTileHandle(LocalWorld &world, IEquipment::MapLevel mapLevel, wvec2 base, cint32 startZ, cint32 endZ);

        bool isReady();
        std::unique_ptr<Tile> toTile();
    };

    std::unordered_map<uint64_t, MapTileHandle> mapTiles;
    std::list<uint64_t> pendingKeys;
    BoundingBox<cint32> visibleArea = {};
    IEquipment::MapLevel level = IEquipment::MapLevel::None;
    TileUpdatedCallback onTileUpdated = [](wvec2, const Tile&, cint32){};

    Player *player = nullptr;
    LocalWorld *localWorld = nullptr;
    int targetZ;

    wvec2 cameraMinBound = wvec2(0, 0);
    wvec2 cameraMaxBound = wvec2(0, 0);

    Controller::Command<uint32_t, IEquipment::MapLevel> updateLevel;
    Controller::Command<uint32_t, glm::uvec2, Tile, cint32> updateTile;

    void loadTilesInRange(wvec2 minBound, wvec2 maxBound);

public:
    static uint64_t coords2key(wvec2 coords);
    static wvec2 key2coords(uint64_t key);

    Controller::Command<uint32_t, glm::uvec2, glm::uvec2> setCameraBounds;
    Controller::Command<uint32_t, std::string, glm::ivec2> placePin;
    Controller::Command<uint32_t, uint32_t, bool> setPinSwitch;
    Controller::Command<uint32_t, uint32_t> deletePin;

    RemoteMap(Controller *ctrl);

    void init(IEquipment::MapLevel level);
    void update();
    void setTileUpdatedCallback(TileUpdatedCallback callback);
    IEquipment::MapLevel getMapLevel();
};

DERIVE_STRUCT_SERIALIZER(RemoteMap::Cell, self.blockId, self.z);
DERIVE_STRUCT_SERIALIZER(RemoteMap::Tile, self.cells);

#endif