use crate::lua::LuaObject;

use std::collections::HashMap;

/// Raw representation of data as it appears in the replay file. This may not be particularly
/// useful with information scattered around various different Lua data types.
#[derive(Debug)]
pub struct Replay {
    pub header: ReplayHeader,
    pub body: ReplayBody,
}

#[derive(Debug)]
pub struct ReplayHeader {
    pub scfa_version: String,
    pub replay_version: String,
    pub map_file: String,
    pub mods: LuaObject,
    pub scenario: LuaObject,
    pub players: HashMap<String, u32>,
    pub cheats_enabled: bool,
    pub army_count: usize,
    pub armies: HashMap<u32, LuaObject>,
    pub seed: u32,
}

#[derive(Debug, PartialEq)]
pub struct ReplayBody {
    pub commands: Vec<ReplayCommand>,
    pub sim: SimData,
}

#[derive(Debug, PartialEq)]
pub struct SimData {
    /// The current tick
    pub tick: u32,
    /// The player id of the current command sender. Only valid if `tick > 0`
    pub command_source: u8,
    /// A map of player id's to the tick on which their last command was received
    pub players_last_tick: HashMap<u8, u32>,
    /// The current checksum value. Only valid if `checksum_tick != None`
    pub checksum: [u8; 16],
    /// The current tick which the checksum is verifying
    pub checksum_tick: Option<u32>,
    /// The first tick that was desynced
    pub desync_tick: Option<u32>,
    /// A list of all ticks that were desynced
    pub desync_ticks: Option<Vec<u32>>,
}
impl SimData {
    pub fn new() -> SimData {
        SimData {
            tick: 0,
            command_source: 0,
            players_last_tick: HashMap::new(),
            checksum: [0; 16],
            checksum_tick: None,
            desync_tick: None,
            desync_ticks: None,
        }
    }
}

/// Command names for the replay body data
pub mod replay_command {
    pub const ADVANCE: u8 = 0;
    pub const SET_COMMAND_SOURCE: u8 = 1;
    pub const COMMAND_SOURCE_TERMINATED: u8 = 2;
    pub const VERIFY_CHECKSUM: u8 = 3;
    pub const REQUEST_PAUSE: u8 = 4;
    pub const RESUME: u8 = 5;
    pub const SINGLE_STEP: u8 = 6;
    pub const CREATE_UNIT: u8 = 7;
    pub const CREATE_PROP: u8 = 8;
    pub const DESTROY_ENTITY: u8 = 9;
    pub const WARP_ENTITY: u8 = 10;
    pub const PROCESS_INFO_PAIR: u8 = 11;
    pub const ISSUE_COMMAND: u8 = 12;
    pub const ISSUE_FACTORY_COMMAND: u8 = 13;
    pub const INCREASE_COMMAND_COUNT: u8 = 14;
    pub const DECREASE_COMMAND_COUNT: u8 = 15;
    pub const SET_COMMAND_TARGET: u8 = 16;
    pub const SET_COMMAND_TYPE: u8 = 17;
    pub const SET_COMMAND_CELLS: u8 = 18;
    pub const REMOVE_COMMAND_FROM_QUEUE: u8 = 19;
    pub const DEBUG_COMMAND: u8 = 20;
    pub const EXECUTE_LUA_IN_SIM: u8 = 21;
    pub const LUA_SIM_CALLBACK: u8 = 22;
    pub const END_GAME: u8 = 23;
    /// The maximum valid command type
    pub const MAX: u8 = 23;

    pub static NAMES: [&str; 24] = [
        "Advance",
        "SetCommandSource",
        "CommandSourceTerminated",
        "VerifyChecksum",
        "RequestPause",
        "Resume",
        "SingleStep",
        "CreateUnit",
        "CreateProp",
        "DestroyEntity",
        "WarpEntity",
        "ProcessInfoPair",
        "IssueCommand",
        "IssueFactoryCommand",
        "IncreaseCommandCount",
        "DecreaseCommandCount",
        "SetCommandTarget",
        "SetCommandType",
        "SetCommandCells",
        "RemoveCommandFromQueue",
        "DebugCommand",
        "ExecuteLuaInSim",
        "LuaSimCallback",
        "EndGame",
    ];
}

#[derive(Debug, PartialEq)]
pub struct ReplayCommandFrame {
    pub command_id: u8,
    /// Size of the entire frame. This is data.len() + 3
    pub size: u16,
    /// Contents of the frame without the frame header.
    pub data: Vec<u8>,
}

#[derive(Debug, PartialEq)]
pub enum ReplayCommand {
    /// Advance for some number of ticks
    Advance {
        ticks: u32,
    },
    SetCommandSource {
        id: u8,
    },
    CommandSourceTerminated,
    /// Verify that all command streams agree
    ///
    /// If one of the command streams does not agree, then the game has desynced.
    VerifyChecksum {
        digest: Vec<u8>,
        tick: u32,
    },
    RequestPause,
    Resume,
    SingleStep,
    /// u8 - Owner army
    /// string - Blueprint identifier
    CreateUnit {
        army: u8,
        blueprint: String,
        x: f32,
        z: f32,
        heading: f32,
    },
    CreateProp {
        blueprint: String,
        position: Position,
    },
    DestroyEntity {
        unit: u32,
    },
    WarpEntity {
        unit: u32,
        x: f32,
        y: f32,
        z: f32,
    },
    /// Set certain unit attributes such as their name or whether or not they are paused
    ProcessInfoPair {
        unit: u32,
        arg1: String,
        arg2: String,
    },
    /// Add a command to a unit's command queue
    IssueCommand(GameCommand),
    /// Add a command to a factory's command queue
    IssueFactoryCommand(GameCommand),
    IncreaseCommandCount {
        id: u32,
        delta: i32,
    },
    DecreaseCommandCount {
        id: u32,
        delta: i32,
    },
    /// CmdId - command id
    /// STITarget - target
    SetCommandTarget {
        id: u32,
        target: Target,
    },
    /// CmdId - command id
    /// EUnitCommandType - type
    SetCommandType {
        id: u32,
        type_: u8,
    },
    /// CmdId - command id
    /// ListOfCells - list of cells
    /// Vector3f - pos
    SetCommandCells {
        id: u32,
        cells: LuaObject,
        position: Position,
    },
    /// CmdId - command id
    /// EntId - unit
    RemoveCommandFromQueue {
        id: u32,
        unit: u32,
    },
    /// string -- the debug command string
    /// Vector3f -- mouse pos (in world coords)
    /// uint8 -- focus army index
    /// EntIdSet -- selection
    DebugCommand {
        command: String,
        position: Position,
        focus_army: u8,
        selection: Vec<u32>,
    },
    /// A lua string to evaluate in the simulation
    ExecuteLuaInSim {
        code: String,
    },
    // From SupremeCommander.exe strings:
    // SimCallback(callback[,bool]): Execute a lua function in sim
    // callback = {
    //    Func    =   function name (in the SimCallbacks.lua module) to call
    //    Args    =   Arguments as a lua object
    // If bool is specified and true, sends the current selection with the command
    LuaSimCallback {
        func: String,
        args: LuaObject,
        /// A list of entity id's that are currently selected
        selection: Vec<u32>,
    },
    EndGame,
}

#[derive(Debug, PartialEq)]
pub struct Position {
    /// East/West direction
    pub x: f32,
    /// Vertical direction
    pub y: f32,
    /// North/South direction
    pub z: f32,
}

pub mod target_type {
    pub const NONE: u8 = 0;
    pub const ENTITY: u8 = 1;
    pub const POSITION: u8 = 2;
}

#[derive(Debug, PartialEq)]
pub enum Target {
    None,
    Entity {
        id: u32,
    },
    /// Where the top left corner of the map is (0, 0, 0)
    Position(Position),
}

/// Quaternion representing orientation and a scale value
#[derive(Debug, PartialEq)]
pub struct Formation {
    pub a: f32,
    pub b: f32,
    pub c: f32,
    pub d: f32,
    pub scale: f32,
}

#[derive(Debug, PartialEq)]
pub struct Cell {
    pub x: i16,
    pub z: i16,
}

/// The data contained by `IssueCommand` and `IssueFactoryCommand`. Some of the fields are
/// unknown as the documentation that used to exist on the gpg forums has been lost.
#[derive(Debug, PartialEq)]
pub struct GameCommand {
    pub entity_ids: Vec<u32>,
    pub id: u32,
    /// References another command in the command queue. This is known to reference another attack
    /// command in the case of a "coordinated attack", but it may be used for other cases too.
    pub coordinated_attack_cmd_id: u32,
    pub type_: u8,
    pub arg2: i32,
    pub target: Target,
    pub arg3: u8,
    pub formation: Option<Formation>,
    pub blueprint: String,
    pub arg4: u32,
    pub arg5: u32,
    pub arg6: u32,
    /// A Lua table for selecting commander upgrades. The table always consists of two keys:
    ///
    /// ```json
    /// {
    ///     "TaskName": "EnhanceTask",
    ///     "Enhancement": "AdvancedEngineering"
    /// }
    /// ```
    pub upgrades: LuaObject,
    /// Indicates whether or not starting this upgrade clears the current command queue.
    /// Only appears if `upgrades` is non empty.
    pub clear_queue: Option<bool>,
}

/// Constants refering to the `EUnitCommandType` enumeration
pub mod game_command {
    pub const NONE: u8 = 0;
    pub const STOP: u8 = 1;
    pub const MOVE: u8 = 2;
    pub const DIVE: u8 = 3;
    pub const FORM_MOVE: u8 = 4;
    pub const BUILD_SILO_TACTICAL: u8 = 5;
    pub const BUILD_SILO_NUKE: u8 = 6;
    pub const BUILD_FACTORY: u8 = 7;
    pub const BUILD_MOBILE: u8 = 8;
    pub const BUILD_ASSIST: u8 = 9;
    pub const ATTACK: u8 = 10;
    pub const FORM_ATTACK: u8 = 11;
    pub const NUKE: u8 = 12;
    pub const TACTICAL: u8 = 13;
    pub const TELEPORT: u8 = 14;
    pub const GUARD: u8 = 15;
    pub const PATROL: u8 = 16;
    pub const FERRY: u8 = 17;
    pub const FORM_PATROL: u8 = 18;
    pub const RECLAIM: u8 = 19;
    pub const REPAIR: u8 = 20;
    pub const CAPTURE: u8 = 21;
    pub const TRANSPORT_LOAD_UNITS: u8 = 22;
    pub const TRANSPORT_REVERSE_LOAD_UNITS: u8 = 23;
    pub const TRANSPORT_UNLOAD_UNITS: u8 = 24;
    pub const TRANSPORT_UNLOAD_SPECIFIC_UNITS: u8 = 25;
    pub const DETACH_FROM_TRANSPORT: u8 = 26;
    pub const UPGRADE: u8 = 27;
    pub const SCRIPT: u8 = 28;
    pub const ASSIST_COMMANDER: u8 = 29;
    pub const KILL_SELF: u8 = 30;
    pub const DESTROY_SELF: u8 = 31;
    pub const SACRIFICE: u8 = 32;
    pub const PAUSE: u8 = 33;
    pub const OVER_CHARGE: u8 = 34;
    pub const AGGRESSIVE_MOVE: u8 = 35;
    pub const FORM_AGGRESSIVE_MOVE: u8 = 36;
    pub const ASSIST_MOVE: u8 = 37;
    pub const SPECIAL_ACTION: u8 = 38;
    pub const DOCK: u8 = 39;
    /// The maximum valid command type
    pub const MAX: u8 = 39;
}
