use clap::Parser;
use crossterm::{event::{self, Event as CEvent, KeyboardEnhancementFlags, KeyEventKind, PushKeyboardEnhancementFlags}, execute, terminal};
use psxreadmap::{ConsoleUI, Event, EventReceiver, Terminal, load_memory_map, UIState};
use std::{io::{self, Stdout}, path::PathBuf, sync::mpsc, thread, time::{Duration, Instant}};
use tool_helper::{Error, exit_with_error, open_output};
use ratatui::backend::CrosstermBackend;

pub const RUN_DUMP_TOOL_IN_WSL:bool = cfg!(windows);

#[derive(Parser)]
#[clap(about = "Opens and scans an ELF file to print extended memory information", long_about = None)]
struct CommandLine {
    #[clap(value_parser, help="Input ELF file for scannning")]
    input:   PathBuf,
    #[clap(long="wsl", help="Run \"objdump\" in WSL", default_value_t=RUN_DUMP_TOOL_IN_WSL)]
    use_wsl: bool,
    #[clap(long="raw", default_value_t=false)]
    raw_dump: bool,
    #[clap(short='o', help="Output a memory map file with running the tool")]
    output: Option<PathBuf>
}

pub fn main() {
    match CommandLine::try_parse() {
        Ok(cmd_line) => {
            match run_main(cmd_line) {
                Ok(_)      => (),
                Err(error) => exit_with_error(error)
            }
        },
        Err(error) => {
            println!("{})", error)
        }
    }
}

fn run_main(cmd: CommandLine) -> Result<(), Error> {
    if cmd.raw_dump {
        psxreadmap::get_tool_output(cmd.use_wsl, cmd.input, open_output(&cmd.output)?)?;
        return Ok(());
    }

    let     memory_map = load_memory_map(cmd.use_wsl, cmd.input)?; dump_memory_map(cmd.output, &memory_map)?;
    let     rx         = start_event_loop();
    let     terminal   = setup_console()?;
    let mut console_ui = ConsoleUI::new(rx, terminal);

    console_ui.update_data(memory_map)?;
    loop {
        match console_ui.update()? {
            UIState::Alive      => (),
            UIState::Render     => {console_ui.render()?;}
            UIState::Terminated => break
        }
    }

    console_ui.close()
}

fn dump_memory_map(output: Option<PathBuf>, memory_map: &readmap::types::MemoryMap) -> Result<(), Error> {
    if let Some(output) = output {
        let output = tool_helper::open_output(&Some(output))?;

        readmap::dump::write(output, &memory_map)?;
    }

    Ok(())
}

fn start_event_loop() -> EventReceiver {
    // Set up a mpsc (multiproducer, single consumer) channel to communicate between the input handler and the rendering loop.
    let (tx, rx)  = mpsc::channel();
    let tick_rate = Duration::from_millis(200);
    thread::spawn(move || {
        let mut last_tick = Instant::now();

        tx.send(Event::ForceRender).expect("Send ForceRender command!");
        loop {
            let timeout = tick_rate.checked_sub(last_tick.elapsed()).unwrap_or_else(|| Duration::from_secs(0));
            if event::poll(timeout).expect("Event poll working") {
                if let CEvent::Key(key) = event::read().expect("Can read key input") {
                    if key.kind == KeyEventKind::Release {
                        tx.send(Event::Input(key)).expect("Can send KeyInput");
                    }
                }
            }

            if last_tick.elapsed() >= tick_rate {
                if let Ok(_) = tx.send(Event::Tick) {
                    last_tick = Instant::now();
                }
            }
        }
    });

    rx
}

fn setup_console() -> Result<Terminal, Error> {
    fn open_stdout() -> Result<Stdout, Error> {
        let mut stdout = io::stdout();
        if cfg!(unix) {
            execute!(stdout, PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::REPORT_EVENT_TYPES))?;
        }
        Ok(stdout)
    }

    terminal::enable_raw_mode()?;

    // Setup Crossterm for the Terminal
    let     stdout   = open_stdout()?;
    let     backend  = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend)?;    
    
    terminal.clear()?;
    Ok(terminal)
}