use std::{
    path::{Path, PathBuf},
    process::ExitCode,
    sync::{Arc, RwLock},
};

use clap::Parser;
use libcolors::{
    application::{
        window::{EventOutcome, Window},
        Application,
    },
    event::KeyEvent,
    geometry::{Point, Rectangle},
    input::Key,
    surface::{MappedSurface, Surface, VecSurface},
};

use crate::{error::Error, format::ImageData};

mod error;
mod format;

#[derive(Debug, Parser)]
struct Args {
    filename: PathBuf,
}

struct State {
    image_surface: VecSurface,
    target_surface: VecSurface,

    scale: ScaleMethod,
}

#[derive(Debug, Clone, Copy)]
pub enum ScaleMethod {
    Fit,
    Identity,
    Maximize(u32), // x scaling
    Minimize(u32), // 1/x scaling
}

impl ScaleMethod {
    pub fn bigger(self) -> ScaleMethod {
        match self {
            Self::Fit => ScaleMethod::Maximize(1),
            Self::Identity => ScaleMethod::Maximize(2),
            Self::Maximize(n) if n < 8 => Self::Maximize(n * 2),
            Self::Minimize(2) => Self::Identity,
            Self::Minimize(n) => Self::Minimize(n / 2),
            _ => self,
        }
    }

    pub fn smaller(self) -> ScaleMethod {
        match self {
            Self::Fit => ScaleMethod::Identity,
            Self::Identity => Self::Minimize(2),
            Self::Maximize(2) => Self::Identity,
            Self::Maximize(n) => Self::Maximize(n / 2),
            Self::Minimize(n) if n < 8 => Self::Minimize(n * 2),
            _ => self,
        }
    }
}

impl State {
    fn load_image<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
        let image = ImageData::load_image(path)?;
        let image_surface = image.create_surface();
        let target_surface = image_surface.clone();
        Ok(Self {
            image_surface,
            target_surface,
            scale: ScaleMethod::Fit,
        })
    }

    fn handle_key(&mut self, key: KeyEvent) -> EventOutcome {
        match key.key {
            Key::Up => {
                self.scale = self.scale.bigger();
                EventOutcome::Redraw
            }
            Key::Down => {
                self.scale = self.scale.smaller();
                EventOutcome::Redraw
            }
            _ => EventOutcome::None,
        }
    }

    fn scale_to_aspect(&mut self, target_width: u32, target_height: u32) {
        let target_aspect = self.image_surface.width() as f32 / self.image_surface.height() as f32;

        let w;
        let h;
        if (target_height as f32 * target_aspect) as u32 > target_width {
            w = target_width;
            h = (target_width as f32 / target_aspect) as u32;
        } else {
            w = (target_height as f32 * target_aspect) as u32;
            h = target_height;
        }

        if self.target_surface.width() == w && self.target_surface.height() == h {
            return;
        }

        let mut destination = VecSurface::new(w, h);
        format::scale_surface(&self.image_surface, &mut destination);
        self.target_surface = destination;
    }

    fn redraw(&mut self, surface: &mut MappedSurface) {
        // Scale to match aspect
        let (target_width, target_height) = match self.scale {
            ScaleMethod::Fit => (surface.width(), surface.height()),
            ScaleMethod::Identity => (self.image_surface.width(), self.image_surface.height()),
            ScaleMethod::Maximize(n) => (
                self.image_surface.width() * n,
                self.image_surface.height() * n,
            ),
            ScaleMethod::Minimize(n) => (
                self.image_surface.width() / n,
                self.image_surface.height() / n,
            ),
        };
        self.scale_to_aspect(target_width, target_height);

        let iw = self.target_surface.width();
        let ih = self.target_surface.height();
        let sw = surface.width();
        let sh = surface.height();

        let center = surface.center();

        let (off_x, w) = if iw > sw {
            ((iw - sw) / 2, sw)
        } else {
            (0, iw)
        };
        let (off_y, h) = if ih > sh {
            ((ih - sh) / 2, sh)
        } else {
            (0, ih)
        };

        let origin = center - Point::new(w / 2, h / 2);
        let rect = Rectangle {
            x: off_x,
            y: off_y,
            w,
            h,
        };

        surface.fill(0);
        surface.copy_rect(&self.target_surface, rect, origin);
    }
}

fn run(args: Args) -> Result<ExitCode, Error> {
    let state = State::load_image(args.filename)?;
    let mut application = Application::new()?;
    let mut window = Window::new(&application)?;
    let state = Arc::new(RwLock::new(state));

    let s = state.clone();
    window.set_on_redraw_requested(move |surface| {
        s.write().unwrap().redraw(surface);
    });
    let s = state.clone();
    window.set_on_key_pressed(move |key| s.write().unwrap().handle_key(key));

    application.add_window(window);

    Ok(application.run())
}

fn main() -> ExitCode {
    logsink::setup_logging(true);
    let args = Args::parse();

    match run(args) {
        Ok(code) => code,
        Err(error) => {
            eprintln!("{error}");
            ExitCode::FAILURE
        }
    }
}
