use crate::{load_replay, print_replay_version};

use faf_replay_parser::scfa::{replay_command, ParserBuilder, ReplayCommand};
use faf_replay_parser::ReplayResult;
use structopt::StructOpt;

use std::path::PathBuf;

#[derive(StructOpt)]
pub struct CommandsArgs {
    /// Path to the replay
    #[structopt(parse(from_os_str))]
    replay: PathBuf,
    /// Limits output to the first <num> commands
    #[structopt(long = "limit")]
    limit: Option<usize>,
    /// Parses all command types
    #[structopt(short = "a", long = "all")]
    all: bool,
    /// Parses these command types
    #[structopt(
        short = "c",
        long = "commands",
        possible_values = &replay_command::NAMES
    )]
    commands: Vec<String>,
    /// Shows commands referenced by other commands when available
    #[structopt(short = "r", long = "references")]
    references: bool,
}

/// Subcommand 'commands'
pub fn print_commands(args: &CommandsArgs) -> ReplayResult<()> {
    let replay_data = load_replay(&args.replay)?;

    let mut builder = ParserBuilder::new().limit(args.limit);

    // Add commands from args
    if args.all {
        builder = builder.commands_all();
    } else if args.commands.len() > 0 {
        builder = builder.commands(
            &args
                .commands
                .iter()
                .map(|c| linear_search(&replay_command::NAMES, &c.as_str()).unwrap() as u8)
                .collect::<Vec<u8>>(),
        )
    } else {
        builder = builder.commands(&[
            replay_command::ADVANCE,
            replay_command::SET_COMMAND_SOURCE,
            replay_command::COMMAND_SOURCE_TERMINATED,
            replay_command::VERIFY_CHECKSUM,
            replay_command::LUA_SIM_CALLBACK,
            replay_command::END_GAME,
        ]);
    }

    // Make the parser
    let parser = builder.build();
    // Parse the replay
    let replay = parser.parse(&mut replay_data.as_slice())?;

    // Print info
    print_replay_version(&replay);
    println!();

    let commands = &replay.body.commands;
    // For whitespace padding smaller numbers so that they line up with later ones
    let digits = (commands.len() as f64).log10().ceil() as usize;
    for (i, cmd) in commands.iter().enumerate() {
        println!("{:w$} ├── {:?}", i, cmd, w = digits);

        if args.references {
            use ReplayCommand::*;
            let mut references = Vec::new();
            if let IssueCommand(gcmd) | IssueFactoryCommand(gcmd) = cmd {
                if gcmd.coordinated_attack_cmd_id != std::u32::MAX {
                    references.push(gcmd.coordinated_attack_cmd_id);
                }
                if gcmd.arg2 != -1 {
                    references.push(gcmd.arg2 as u32);
                }
            }
            if let IncreaseCommandCount { id, .. }
            | DecreaseCommandCount { id, .. }
            | RemoveCommandFromQueue { id, .. } = cmd
            {
                references.push(*id);
            }

            for reference in references {
                // Try to find the command
                for cmd in commands.iter() {
                    if let IssueCommand(gcmd) | IssueFactoryCommand(gcmd) = cmd {
                        if gcmd.id == reference {
                            println!("{:w$} │   ├── {:?}", ' ', cmd, w = digits);
                        }
                    }
                }
            }
        }
    }

    println!();
    println!("Total commands parsed: {}", commands.len());

    Ok(())
}

fn linear_search<T: PartialEq>(slice: &[T], item: &T) -> Option<usize> {
    for (i, t) in slice.iter().enumerate() {
        if t == item {
            return Some(i);
        }
    }
    return None;
}
