use std::{
    io, thread,
    time::{Duration, Instant},
};

use anyhow::{anyhow, Result};
use palette::{Hsl, IntoColor, Srgb};
use serialport::SerialPort;

mod fixtures;

const FPS: u32 = 50;

enum MCUResponse {
    Sync,
    Ack,
    Info { num_pkts: u32 },
}

fn poll_response(ser: &mut dyn SerialPort) -> Result<MCUResponse> {
    let mut read_buffer = vec![0u8; 32];
    let bytes_read;
    loop {
        match ser.read(read_buffer.as_mut_slice()) {
            Ok(t) => {
                bytes_read = t;
                break;
            }
            Err(ref e) if e.kind() == io::ErrorKind::TimedOut => continue,
            Err(e) => Err(e),
        }?
    }

    let response = std::str::from_utf8(&read_buffer[..bytes_read])?;

    match response.trim() {
        "Sync." => Ok(MCUResponse::Sync),
        "Ack." => Ok(MCUResponse::Ack),
        s if s.starts_with("Info") => Ok(MCUResponse::Info { num_pkts: 69 }),
        s => Err(anyhow!("Unknown response: \"{}\"", s)),
    }
}

fn main() -> Result<()> {
    let frame_time = Duration::from_secs_f64(1.0 / FPS as f64);

    let mut dmx_buffer = [0u8; 512];

    let mut movingheads = [
        fixtures::MovingHead14CH::new(1),
        fixtures::MovingHead14CH::new(15),
        fixtures::MovingHead14CH::new(29),
        fixtures::MovingHead14CH::new(43),
    ];

    for movinghead in movingheads.iter_mut() {
        movinghead.dimmer = 134;
    }

    let mut ser = serialport::new("/dev/ttyUSB0", 500_000)
        .timeout(Duration::from_millis(10))
        .open()?;
    println!("open");

    // wait for initial sync
    loop {
        match poll_response(&mut *ser) {
            Ok(MCUResponse::Sync) => break,
            _ => continue,
        }
    }

    println!("sync");

    let mut t = 0;

    'main: loop {
        let loop_start = Instant::now();

        let dist = FPS as f32 / movingheads.len() as f32;

        for (i, movinghead) in movingheads.iter_mut().enumerate() {
            let hsl_color = Hsl::new(
                360.0 * ((t % FPS) as f32 / FPS as f32) + i as f32 * dist,
                1.0,
                0.5,
            );
            movinghead.rgb = hsl_color.into_color();

            movinghead.render(&mut dmx_buffer);
        }

        // write DMX data
        let write_result = ser.write(&dmx_buffer);

        if write_result.is_err() {
            loop {
                match poll_response(&mut *ser) {
                    Ok(MCUResponse::Sync) => {
                        continue 'main;
                    }
                    _ => continue,
                }
            }
        }

        // get response
        loop {
            match poll_response(&mut *ser) {
                Ok(MCUResponse::Ack) => break,
                Ok(MCUResponse::Info { num_pkts }) => {
                    println!("Info: {}", num_pkts);
                    continue;
                }
                Err(e) => {
                    println!("Error! {:?}", e);
                    continue;
                }
                Ok(MCUResponse::Sync) => {
                    continue 'main;
                }
            }
        }

        t += 1;

        let loop_time = loop_start.elapsed();

        if loop_time < frame_time {
            thread::sleep(frame_time - loop_time);
        } else {
            println!("loop took too long!");
        }

        println!(
            "loop time: {}ms busy, {}ms total",
            loop_time.as_millis(),
            loop_start.elapsed().as_millis()
        );
    }
}
