use base64::encode;
use image::{imageops::Triangle, open, GenericImageView, ImageOutputFormat::Png};
use std::{io::Write, path::Path, sync::mpsc::Receiver};
use termion::{
    cursor::{Goto, Restore, Save},
    terminal_size, terminal_size_pixels,
};

macro_rules! command {
    ($opt:literal, $data:expr) => {
        format!("\x1B_G{};{}\x1B\\", $opt, std::str::from_utf8($data)?).as_str()
    };
}

pub fn preview<P: AsRef<Path>>(
    path: P,
    text_width: u16,
    stdout: &mut impl Write,
    reciever: Receiver<()>,
) -> Result<(), Box<dyn std::error::Error>> {
    if let Ok(image) = open(path) {
        let (cols, rows) = terminal_size()?;
        let (w, h) = terminal_size_pixels()?;
        let w = (w as f32 * ((cols - text_width) as f32 / cols as f32)) as u32;
        let h = (h as f32 * ((rows - 1) as f32 / rows as f32)) as u32;

        let mut data = Vec::new();
        let (image_w, image_h) = image.dimensions();
        if image_w > w || image_h > h {
            image.resize(w, h, Triangle).write_to(&mut data, Png)?;
        } else {
            image.write_to(&mut data, Png)?;
        }

        let data = encode(&data);
        let mut data = data.as_bytes().chunks(4096).peekable();

        let chunk = data.next().ok_or("no image data")?;
        let mut command = format!("{}{}", Save, Goto(text_width as u16 + 1, 1));

        if data.peek().is_none() {
            command += command!("f=100,a=T", chunk);
        } else {
            command += command!("f=100,a=T,m=1", chunk);
            let mut chunk = data.next().ok_or("no image data")?;
            while data.peek().is_some() {
                command += command!("m=1", chunk);
                chunk = data.next().ok_or("no image data")?;
            }
            command += command!("m=0", chunk);
        }
        command += format!("{}", Restore).as_str();
        if reciever.try_recv().is_ok() {
            return Ok(());
        }
        write!(stdout, "{}", command)?;
        stdout.flush()?;
    }
    Ok(())
}
