#include "ConfigLoader.h"

#include <optional>
#include <string>

#include "toml.hpp"

#include "TomlHelpers.h"

WorldConfig ConfigLoader::loadWorld(const std::string& path)
{
    const std::string file = "world.toml";
    toml::table tbl = utility::parseFile(path, file);

    WorldConfig cfg;

    cfg.heightTiles                = static_cast<int>(utility::requireInt(tbl["world"]["height_tiles"], file, "world.height_tiles"));
    cfg.refundPercentage           = static_cast<int>(utility::requireInt(tbl["world"]["refund_percentage"], file, "world.refund_percentage"));
    cfg.deconstructionTimeSeconds  = utility::requireDouble(tbl["world"]["deconstruction_time_seconds"], file, "world.deconstruction_time_seconds");
    cfg.startingBuildingBlocks     = static_cast<int>(utility::requireInt(tbl["world"]["starting_building_blocks"], file, "world.starting_building_blocks"));
    cfg.debrisDespawnSeconds        = utility::requireDouble(tbl["world"]["debris_despawn_seconds"], file, "world.debris_despawn_seconds");
    cfg.scrapPerThreat             = utility::requireDouble(tbl["world"]["scrap_per_threat"], file, "world.scrap_per_threat");
    cfg.tileSize_m                 = utility::requireDouble(tbl["world"]["tile_size_m"], file, "world.tile_size_m");
    cfg.beltSpeed_tps              = utility::requireDouble(tbl["world"]["belt_speed_mps"], file, "world.belt_speed_mps") / cfg.tileSize_m;
    cfg.tunnelMaxDistance_tiles    = static_cast<int>(utility::requireInt(tbl["world"]["tunnel_max_distance_tiles"], file, "world.tunnel_max_distance_tiles"));
    cfg.departureIntervalSeconds   = utility::requireDouble(tbl["world"]["departure_interval_seconds"], file, "world.departure_interval_seconds");
    cfg.orbitFactor                = utility::requireDouble(tbl["world"]["orbit_factor"], file, "world.orbit_factor");
    cfg.rallyOrbitRadius_tiles     = utility::requireDouble(tbl["world"]["rally_orbit_radius_tiles"], file, "world.rally_orbit_radius_tiles");

    if (const std::optional<std::string> tip =
            tbl["world"]["artifact_tooltip"].value<std::string>())
    {
        cfg.artifactTooltip = *tip;
    }

    cfg.regions.asteroidWidth_tiles     = static_cast<int>(utility::requireInt(tbl["regions"]["asteroid_width_tiles"],      file, "regions.asteroid_width_tiles"));
    cfg.regions.playerBufferWidth_tiles = static_cast<int>(utility::requireInt(tbl["regions"]["player_buffer_width_tiles"], file, "regions.player_buffer_width_tiles"));
    cfg.regions.contestZoneWidth_tiles  = static_cast<int>(utility::requireInt(tbl["regions"]["contest_zone_width_tiles"],  file, "regions.contest_zone_width_tiles"));
    cfg.regions.enemyBufferWidth_tiles  = static_cast<int>(utility::requireInt(tbl["regions"]["enemy_buffer_width_tiles"],  file, "regions.enemy_buffer_width_tiles"));

    cfg.expansion.columnsPerExpansion_tiles   = static_cast<int>(utility::requireInt(tbl["expansion"]["columns_per_expansion_tiles"], file, "expansion.columns_per_expansion_tiles"));
    cfg.expansion.costBuildingBlocksFormula   = utility::requireFormula(tbl["expansion"]["cost_building_blocks_formula"],            file, "expansion.cost_building_blocks_formula");

    cfg.push.pushExpandColumns_tiles = static_cast<int>(utility::requireInt(tbl["push"]["push_expand_columns_tiles"], file, "push.push_expand_columns_tiles"));
    cfg.push.bossAdvanceSeconds = utility::requireDouble(tbl["push"]["boss_advance_seconds"], file, "push.boss_advance_seconds");

    cfg.waves.threatRateFormula         = utility::requireFormula(tbl["waves"]["threat_rate_formula"],         file, "waves.threat_rate_formula");
    cfg.waves.gapMinSeconds             = utility::requireDouble(tbl["waves"]["gap_min_seconds"],              file, "waves.gap_min_seconds");
    cfg.waves.gapMaxSeconds             = utility::requireDouble(tbl["waves"]["gap_max_seconds"],              file, "waves.gap_max_seconds");
    cfg.waves.spawnDurationSeconds      = utility::requireDouble(tbl["waves"]["spawn_duration_seconds"],       file, "waves.spawn_duration_seconds");
    cfg.waves.bossCountdownSeconds      = utility::requireDouble(tbl["waves"]["boss_countdown_seconds"],       file, "waves.boss_countdown_seconds");
    cfg.waves.bossThreatDurationSeconds = utility::requireDouble(tbl["waves"]["boss_threat_duration_seconds"], file, "waves.boss_threat_duration_seconds");
    cfg.waves.bossQuietBeforeSeconds    = utility::requireDouble(tbl["waves"]["boss_quiet_before_seconds"],    file, "waves.boss_quiet_before_seconds");
    cfg.waves.bossQuietAfterSeconds     = utility::requireDouble(tbl["waves"]["boss_quiet_after_seconds"],     file, "waves.boss_quiet_after_seconds");

    if (cfg.waves.gapMinSeconds > cfg.waves.gapMaxSeconds)
    {
        throw utility::makeError(file, "waves", "gap_min_seconds > gap_max_seconds");
    }

    cfg.targeting.targetScoreFormula      = utility::requireFormula(tbl["targeting"]["target_score_formula"],      file, "targeting.target_score_formula");
    cfg.targeting.overclaimPenaltyFormula = utility::requireFormula(tbl["targeting"]["overclaim_penalty_formula"], file, "targeting.overclaim_penalty_formula");
    cfg.targeting.hysteresis              = utility::requireDouble(tbl["targeting"]["target_hysteresis"],          file, "targeting.target_hysteresis");

    cfg.artifacts.artifactChanceFormula = utility::requireFormula(tbl["artifacts"]["artifact_chance_formula"], file, "artifacts.artifact_chance_formula");
    cfg.artifacts.artifactWinCount      = static_cast<int>(utility::requireInt(tbl["artifacts"]["artifact_win_count"], file, "artifacts.artifact_win_count"));

    cfg.scroll.panSpeedSlow_tps       = utility::requireDouble(tbl["scroll"]["pan_speed_slow_tiles_per_second"], file, "scroll.pan_speed_slow_tiles_per_second");
    cfg.scroll.panSpeedFast_tps       = utility::requireDouble(tbl["scroll"]["pan_speed_fast_tiles_per_second"], file, "scroll.pan_speed_fast_tiles_per_second");
    cfg.scroll.panRampBandWidth_tiles = static_cast<int>(utility::requireInt(tbl["scroll"]["pan_ramp_band_width_tiles"], file, "scroll.pan_ramp_band_width_tiles"));

    return cfg;
}
