use crate::{
    print::{println_debug, println_info, println_warning},
    simulation::{Simulation, SimulationEvent},
    Cli,
};
use crossterm::{
    event,
    event::{Event, KeyCode, KeyEvent, KeyEventKind},
    execute,
    terminal::{
        disable_raw_mode, enable_raw_mode, EnableLineWrap, EnterAlternateScreen,
        LeaveAlternateScreen,
    },
};
use servicepoint::{
    Bitmap, BitmapCommand, BrightnessGrid, BrightnessGridCommand, UdpSocketExt,
    FRAME_PACING, TILE_HEIGHT, TILE_WIDTH,
};
use std::{
    io::stdout,
    net::UdpSocket,
    thread,
    time::{Duration, Instant},
};

pub(crate) struct App {
    connection: UdpSocket,
    sim: Simulation,
    target_duration: Duration,
    pixels: Bitmap,
    luma: BrightnessGrid,
    terminated: bool,
}

impl App {
    pub fn new(cli: Cli) -> Self {
        let connection = UdpSocket::bind_connect(cli.destination)
            .expect("Could not connect. Did you forget `--destination`?");

        execute!(stdout(), EnterAlternateScreen, EnableLineWrap)
            .expect("could not enter alternate screen");
        enable_raw_mode().expect("could not enable raw terminal mode");

        Self {
            connection,
            sim: Simulation::new(),
            terminated: false,
            target_duration: FRAME_PACING * 4,
            pixels: Bitmap::max_sized(),
            luma: BrightnessGrid::new(TILE_WIDTH, TILE_HEIGHT),
        }
    }

    pub(crate) fn run_iteration(&mut self) {
        let start = Instant::now();
        self.sim.run_iteration();

        self.sim.draw_state(&mut self.pixels, &mut self.luma);
        let cmd: BitmapCommand = self.pixels.clone().into();
        self.connection.send_command(cmd).unwrap();
        let cmd: BrightnessGridCommand = self.luma.clone().into();
        self.connection.send_command(cmd).unwrap();

        self.poll_events();

        let tick_time = start.elapsed();
        if tick_time < self.target_duration {
            thread::sleep(self.target_duration - tick_time);
        }
    }

    pub(crate) fn terminated(&self) -> bool {
        self.terminated
    }

    fn poll_events(&mut self) -> bool {
        while event::poll(Duration::from_secs(0)).expect("could not poll") {
            let event = event::read().expect("could not read event");

            if let Event::Key(KeyEvent {
                kind: KeyEventKind::Press,
                code,
                ..
            }) = event
            {
                if let Some(sim_event) = self.handle_key(code) {
                    self.sim.handle_event(sim_event);
                };
            } else {
                println_debug(format!("unhandled event {event:?}"));
            }
        }
        false
    }

    fn handle_key(&mut self, code: KeyCode) -> Option<SimulationEvent> {
        match code {
            KeyCode::Char('d') => Some(SimulationEvent::RandomizeLeftPixels),
            KeyCode::Char('e') => Some(SimulationEvent::RandomizeLeftLuma),
            KeyCode::Char('f') => Some(SimulationEvent::RandomizeRightPixels),
            KeyCode::Char('r') => Some(SimulationEvent::RandomizeRightLuma),
            KeyCode::Right => Some(SimulationEvent::SeparatorAccelerate),
            KeyCode::Left => Some(SimulationEvent::SeparatorDecelerate),
            KeyCode::Char('h') => {
                println_info("[h] help");
                println_info("[q] quit");
                println_info("[d] randomize left pixels");
                println_info("[e] randomize left luma");
                println_info("[r] randomize right pixels");
                println_info("[f] randomize right luma");
                println_info("[→] accelerate divider right");
                println_info("[←] accelerate divider left");
                None
            }
            KeyCode::Char('q') => {
                println_warning("terminating");
                self.terminated = true;
                None
            }
            KeyCode::Up => {
                self.target_duration = self
                    .target_duration
                    .saturating_sub(Duration::from_millis(1));
                println_info(format!(
                    "increased simulation speed to {} ups",
                    1f64 / self.target_duration.as_secs_f64()
                ));
                None
            }
            KeyCode::Down => {
                self.target_duration = self
                    .target_duration
                    .saturating_add(Duration::from_millis(1));
                println_info(format!(
                    "decreased simulation speed to {} ups",
                    1f64 / self.target_duration.as_secs_f64()
                ));
                None
            }
            key_code => {
                println_debug(format!("unhandled KeyCode {key_code:?}"));
                None
            }
        }
    }
}

impl Drop for App {
    fn drop(&mut self) {
        disable_raw_mode().expect("could not disable raw terminal mode");
        execute!(stdout(), LeaveAlternateScreen).expect("could not leave alternate screen");
    }
}