use super::replay::*;
use crate::lua::{LuaObject, LuaTable};
use crate::scfa::Replay;
use std::collections::HashMap;
use std::time::Duration;

/// Try to extract useful information out of a replay.
///
/// The `.sfcareplay` format is a jumble of mostly Lua tables that can contain arbitrary information.
/// Moreover, a lot of common information (such as information about players or information about
/// the map) is spread accross multiple different such tables. This function searches through all
/// those data structures and combines the information into a friendlier format.
pub fn aggregate_replay_info(replay: &Replay) -> ReplayInfo {
    let duration = if replay.body.sim.tick == 0 {
        None
    } else {
        Some(Duration::from_millis(replay.body.sim.tick as u64 * 100))
    };

    let options = replay
        .header
        .scenario
        .as_hashmap()
        .ok()
        .and_then(|t| t.get(&LuaObject::from("Options")))
        .and_then(|obj| obj.as_hashmap().ok());

    let quality = options
        .and_then(|t| t.get(&LuaObject::from("Quality")))
        .and_then(|obj| obj.as_float().ok());

    let ranked = options
        .and_then(|t| t.get(&LuaObject::from("RankedGame")))
        .and_then(|obj| obj.to_string().ok())
        .map(|s| s == "On");

    ReplayInfo {
        scfa_version: replay.header.scfa_version.clone(),
        replay_version: replay.header.replay_version.clone(),
        seed: replay.header.seed,
        duration,
        quality,
        ranked,
        map: aggregate_replay_map_info(replay),
        mods: aggregate_replay_mods_info(replay),
        options: aggregate_replay_options_info(replay),
        players: aggregate_replay_players_info(replay),
    }
}

pub fn aggregate_replay_map_info(replay: &Replay) -> ReplayMapInfo {
    let scenario = replay.header.scenario.as_hashmap().ok();

    // Directly from the "Scenario" table
    let name = scenario
        .and_then(|t| t.get(&LuaObject::from("name")))
        .and_then(|obj| obj.to_string_lossy().ok());
    let description = scenario
        .and_then(|t| t.get(&LuaObject::from("description")))
        .and_then(|obj| obj.to_string_lossy().ok());
    let script_file = scenario
        .and_then(|t| t.get(&LuaObject::from("script")))
        .and_then(|obj| obj.to_string().ok());
    let save_file = scenario
        .and_then(|t| t.get(&LuaObject::from("save")))
        .and_then(|obj| obj.to_string().ok());
    let preview = scenario
        .and_then(|t| t.get(&LuaObject::from("preview")))
        .and_then(|obj| obj.to_string().ok());

    // From the "Options" sub table
    let scenario_file = scenario
        .and_then(|t| t.get(&LuaObject::from("Options")))
        .and_then(|obj| obj.as_hashmap().ok())
        .and_then(|t| t.get(&LuaObject::from("ScenarioFile")))
        .and_then(|obj| obj.to_string().ok());

    ReplayMapInfo {
        map_file: replay.header.map_file.clone(),
        name,
        description,
        script_file,
        save_file,
        preview,
        scenario_file,
    }
}

pub fn aggregate_replay_mods_info(_replay: &Replay) -> ReplayModsInfo {
    unimplemented!()
}

pub fn aggregate_replay_options_info(replay: &Replay) -> ReplayOptionsInfo {
    let options = replay
        .header
        .scenario
        .as_hashmap()
        .ok()
        .and_then(|t| t.get(&LuaObject::from("Options")))
        .and_then(|obj| obj.as_hashmap().ok());

    let title = options
        .and_then(|t| t.get(&LuaObject::from("Title")))
        .and_then(|obj| obj.to_string().ok());

    let replay_id = options
        .and_then(|t| t.get(&LuaObject::from("ReplayId")))
        .and_then(|obj| obj.to_string().ok());

    let victory = options
        .and_then(|t| t.get(&LuaObject::from("Victory")))
        .and_then(|obj| obj.to_string().ok());

    let unit_cap = options
        .and_then(|t| t.get(&LuaObject::from("UnitCap")))
        .and_then(|obj| obj.to_string().ok())
        .and_then(|s| s.parse::<u32>().ok());

    let cheats = options
        .and_then(|t| t.get(&LuaObject::from("CheatsEnabled")))
        .and_then(|obj| obj.to_string().ok())
        .and_then(|s| s.parse::<bool>().ok());

    let cheat_mult = options
        .and_then(|t| t.get(&LuaObject::from("CheatMult")))
        .and_then(|obj| obj.to_string().ok())
        .and_then(|s| s.parse::<f32>().ok());

    let build_mult = options
        .and_then(|t| t.get(&LuaObject::from("BuildMult")))
        .and_then(|obj| obj.to_string().ok())
        .and_then(|s| s.parse::<f32>().ok());

    let share = options
        .and_then(|t| t.get(&LuaObject::from("Share")))
        .and_then(|obj| obj.to_string().ok());

    ReplayOptionsInfo {
        title,
        replay_id,
        victory,
        unit_cap,
        cheats,
        cheat_mult,
        build_mult,
        share,
    }
}

pub fn aggregate_replay_players_info(replay: &Replay) -> HashMap<String, ReplayPlayerInfo> {
    let mut players = HashMap::new();

    // TODO: Use replay.players?
    for (i, army) in &replay.header.armies {
        if let Some(army) = army.as_hashmap().ok() {
            let player_info = aggregate_player_info_from_army(army);
            players.insert(
                player_info
                    .name
                    .clone()
                    .unwrap_or_else(|| format!("Unknown{}", i)),
                player_info,
            );
        }
    }

    // Now add information from the Scenario.Options table
    if let Some(scenario) = replay
        .header
        .scenario
        .as_hashmap()
        .ok()
        .and_then(|t| t.get(&LuaObject::from("Options")))
        .and_then(|obj| obj.as_hashmap().ok())
    {
        // Ratings
        let ratings = scenario
            .get(&LuaObject::from("Ratings"))
            .and_then(|obj| obj.as_hashmap().ok());
        if let Some(ratings) = ratings {
            for (player, rating) in ratings {
                let player = player.to_string().ok();
                let rating = rating.as_float().ok();
                // Only update the rating if it's not empty
                match (player, rating) {
                    (Some(player), Some(rating)) => players
                        .get_mut(&player)
                        .map(|p| p.rating = Some(rating as i32)),
                    _ => None,
                };
            }
        }
        // Clan tags
        let clan_tags = scenario
            .get(&LuaObject::from("ClanTags"))
            .and_then(|obj| obj.as_hashmap().ok());
        if let Some(clan_tags) = clan_tags {
            for (player, clan) in clan_tags {
                let player = player.to_string().ok();
                let clan = clan.to_string().ok();
                // Only update the clan tag if it's not empty
                match (player, clan) {
                    (Some(ref player), Some(ref clan)) if !clan.is_empty() => players
                        .get_mut(player)
                        .map(|p| p.clan.replace(clan.clone())),
                    _ => None,
                };
            }
        }
    }

    players
}

fn aggregate_player_info_from_army(army: &LuaTable) -> ReplayPlayerInfo {
    let id = army
        .get(&LuaObject::from("OwnerID"))
        .and_then(|obj| obj.to_string().ok())
        .and_then(|s| s.parse().ok());
    let name = army
        .get(&LuaObject::from("PlayerName"))
        .and_then(|obj| obj.to_string().ok());
    let human = army
        .get(&LuaObject::from("Human"))
        .and_then(|obj| obj.as_bool().ok());
    let mean = army
        .get(&LuaObject::from("MEAN"))
        .and_then(|obj| obj.as_float().ok());
    let deviation = army
        .get(&LuaObject::from("DEV"))
        .and_then(|obj| obj.as_float().ok());
    let clan = army
        .get(&LuaObject::from("PlayerClan"))
        .and_then(|obj| obj.to_string().ok())
        .filter(|s| !s.is_empty());
    let country = army
        .get(&LuaObject::from("Country"))
        .and_then(|obj| obj.to_string().ok());
    let start_spot = army
        .get(&LuaObject::from("StartSpot"))
        .and_then(|obj| obj.as_float().ok())
        .map(|f| f as u32);
    let team = army
        .get(&LuaObject::from("Team"))
        .and_then(|obj| obj.as_float().ok())
        .map(|f| f as u32);
    let faction = army
        .get(&LuaObject::from("Faction"))
        .and_then(|obj| obj.as_float().ok())
        .map(|f| f as u32);
    let army_color = army
        .get(&LuaObject::from("ArmyColor"))
        .and_then(|obj| obj.as_float().ok())
        .map(|f| f as u32);
    let player_color = army
        .get(&LuaObject::from("PlayerColor"))
        .and_then(|obj| obj.as_float().ok())
        .map(|f| f as u32);
    let army_name = army
        .get(&LuaObject::from("ArmyName"))
        .and_then(|obj| obj.to_string().ok());

    // AI only
    let ai_personality = army
        .get(&LuaObject::from("AIPersonality"))
        .and_then(|obj| obj.to_string().ok())
        .filter(|s| !s.is_empty());
    let civilian = army
        .get(&LuaObject::from("Civilian"))
        .and_then(|obj| obj.as_bool().ok());

    // Unknown
    let ng = army
        .get(&LuaObject::from("NG"))
        .and_then(|obj| obj.as_float().ok())
        .map(|f| f as i32);
    let pl = army
        .get(&LuaObject::from("PL"))
        .and_then(|obj| obj.as_float().ok())
        .map(|f| f as i32);
    let rc = army
        .get(&LuaObject::from("RC"))
        .and_then(|obj| obj.to_string().ok());

    ReplayPlayerInfo {
        id,
        name,
        human,
        rating: None,
        mean,
        deviation,
        clan,
        country,
        start_spot,
        team,
        faction,
        army_color,
        player_color,
        army_name,
        ai_personality,
        civilian,
        ng,
        pl,
        rc,
    }
}
