#pragma once

#include "GTile.h"
#include "warfactory/ASerializable.h"

namespace warfactory {

class GMap : public ASerializable {
private:
    int width, height;
    GTile* tiles; // Contiguous memory block: tiles[y * width + x]

public:
    GMap(int w, int h);
    ~GMap();

    // Prevent copy (expensive with large maps)
    GMap(const GMap&) = delete;
    GMap& operator=(const GMap&) = delete;

    // Move constructor/assignment for efficiency
    GMap(GMap&& other) noexcept;
    GMap& operator=(GMap&& other) noexcept;

    // ========================================
    // MAP ACCESS
    // ========================================

    GTile* getTile(int x, int y);
    const GTile* getTile(int x, int y) const;

    bool isValidCoordinate(int x, int y) const {
        return x >= 0 && x < width && y >= 0 && y < height;
    }

    int getWidth() const { return width; }
    int getHeight() const { return height; }

    // ========================================
    // MAP STATISTICS
    // ========================================

    /**
     * @brief Get average temperature across map
     */
    float getAverageTemperature() const;

    /**
     * @brief Get average elevation across map
     */
    float getAverageElevation() const;

    // ========================================
    // ITERATION SUPPORT (cache-friendly)
    // ========================================

    GTile* begin() { return tiles; }
    GTile* end() { return tiles + (width * height); }
    const GTile* begin() const { return tiles; }
    const GTile* end() const { return tiles + (width * height); }

    // ========================================
    // SERIALIZATION
    // ========================================

    json serialize() const override;
    void deserialize(const json& data) override;

private:
    void allocateTiles();
    void deallocateTiles();
};

// ========================================
// INLINE IMPLEMENTATIONS
// ========================================

inline GMap::GMap(int w, int h) : ASerializable("gmap"), width(w), height(h), tiles(nullptr) {
    allocateTiles();
}

inline GMap::~GMap() {
    deallocateTiles();
}

inline GMap::GMap(GMap&& other) noexcept
    : ASerializable("gmap"), width(other.width), height(other.height), tiles(other.tiles) {
    other.width = 0;
    other.height = 0;
    other.tiles = nullptr;
}

inline GMap& GMap::operator=(GMap&& other) noexcept {
    if (this != &other) {
        deallocateTiles();
        width = other.width;
        height = other.height;
        tiles = other.tiles;

        other.width = 0;
        other.height = 0;
        other.tiles = nullptr;
    }
    return *this;
}

inline GTile* GMap::getTile(int x, int y) {
    if (!isValidCoordinate(x, y)) return nullptr;
    return &tiles[y * width + x];
}

inline const GTile* GMap::getTile(int x, int y) const {
    if (!isValidCoordinate(x, y)) return nullptr;
    return &tiles[y * width + x];
}

inline float GMap::getAverageTemperature() const {
    if (width == 0 || height == 0) return 0.0f;

    double temperature_sum = 0.0;
    const int total_tiles = width * height;

    for (int i = 0; i < total_tiles; ++i) {
        temperature_sum += tiles[i].getTemperatureCelsius();
    }

    return static_cast<float>(temperature_sum / total_tiles);
}

inline float GMap::getAverageElevation() const {
    if (width == 0 || height == 0) return 0.0f;

    double elevation_sum = 0.0;
    const int total_tiles = width * height;

    for (int i = 0; i < total_tiles; ++i) {
        elevation_sum += tiles[i].getElevationMeters();
    }

    return static_cast<float>(elevation_sum / total_tiles);
}

inline json GMap::serialize() const {
    json data;
    data["width"] = width;
    data["height"] = height;
    data["tiles"] = json::array();

    const int total_tiles = width * height;
    for (int i = 0; i < total_tiles; ++i) {
        const GTile& tile = tiles[i];
        json tile_data;
        tile_data["elevation"] = tile.elevation;
        tile_data["temperature"] = tile.temperature;
        tile_data["biome_type_id"] = tile.biome_type_id;
        tile_data["water_token"] = tile.water_token;
        tile_data["wind_token"] = tile.wind_token;
        tile_data["target_budget_score"] = tile.target_budget_score;
        tile_data["destruction_token"] = tile.destruction_token;
        tile_data["flags"] = tile.flags;
        tile_data["feature_set_id"] = tile.feature_set_id;
        data["tiles"].push_back(tile_data);
    }

    return data;
}

inline void GMap::deserialize(const json& data) {
    deallocateTiles();

    width = data.at("width").get<int>();
    height = data.at("height").get<int>();

    allocateTiles();

    const json& tiles_data = data.at("tiles");
    const int total_tiles = width * height;

    for (int i = 0; i < total_tiles && i < static_cast<int>(tiles_data.size()); ++i) {
        const json& tile_data = tiles_data[i];
        GTile& tile = tiles[i];

        tile.elevation = tile_data.at("elevation").get<uint16_t>();
        tile.temperature = tile_data.at("temperature").get<int16_t>();
        tile.biome_type_id = tile_data.at("biome_type_id").get<uint16_t>();
        tile.water_token = tile_data.at("water_token").get<uint8_t>();
        tile.wind_token = tile_data.at("wind_token").get<uint8_t>();
        tile.target_budget_score = tile_data.at("target_budget_score").get<int8_t>();
        tile.destruction_token = tile_data.at("destruction_token").get<uint16_t>();
        tile.flags = tile_data.at("flags").get<uint16_t>();
        tile.feature_set_id = tile_data.at("feature_set_id").get<uint32_t>();
    }
}

inline void GMap::allocateTiles() {
    if (width > 0 && height > 0) {
        const int total_tiles = width * height;
        tiles = new GTile[total_tiles];
    }
}

inline void GMap::deallocateTiles() {
    delete[] tiles;
    tiles = nullptr;
}

} // namespace warfactory