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

use anyhow::{anyhow, Result};
use serialport::SerialPort;

use crate::fixtures::{DMXFixture, MovingHead};

const FPS: u32 = 50;

enum MCUResponse {
    Sync,
    Ack,
    #[allow(dead_code)]
    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)),
    }
}

pub fn controller_thread(
    running: Arc<Mutex<bool>>,
    movingheads: Arc<Mutex<[MovingHead; 4]>>,
) -> Result<()> {
    let frame_time = Duration::from_secs_f64(1.0 / FPS as f64);

    let mut dmx_buffer = [0u8; 512];

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

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

    'main: loop {
        {
            let running = running.lock().unwrap();

            if !*running {
                break Ok(());
            }
        }

        let loop_start = Instant::now();

        {
            let movingheads = movingheads.lock().unwrap();
            for head in movingheads.iter() {
                head.render(&mut dmx_buffer);
            }
        }

        let write_result = ser.write(&dmx_buffer);

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

        loop {
            match poll_response(&mut *ser) {
                Ok(MCUResponse::Ack) => break,
                Ok(MCUResponse::Info { .. }) | Err(_) => continue,
                Ok(MCUResponse::Sync) => continue 'main,
            }
        }

        let loop_time = loop_start.elapsed();

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