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

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

enum MCUResponse {
    Sync,
    Ack,
}

const FPS: u32 = 50;

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 => Err(anyhow!("Unknown response: \"{}\"", s)),
    }
}

pub struct Controller {
    pub state: Arc<Mutex<SharedState>>,
    thread: Option<JoinHandle<Result<(), Error>>>,
}

impl Controller {
    pub fn new(path: String) -> Result<Self> {
        let state = Arc::new(Mutex::new(SharedState {
            running: true,
            data: [0; 512],
        }));

        let ser = serialport::new(path, 500_000)
            .timeout(Duration::from_millis(10))
            .open()?;

        let handle = {
            let state = state.clone();
            thread::spawn(move || controller_thread(ser, state))
        };

        Ok(Self {
            state,
            thread: Some(handle),
        })
    }

    pub fn stop(&mut self) -> Result<()> {
        {
            let mut state = self.state.lock().unwrap();
            state.running = false;
        }
        if self.thread.is_some() {
            self.thread.take().unwrap().join().unwrap()
        } else {
            Err(anyhow!("thread was already closed"))
        }
    }
}

pub struct SharedState {
    running: bool,
    pub data: [u8; 512],
}

fn controller_thread(mut ser: Box<dyn SerialPort>, state: Arc<Mutex<SharedState>>) -> Result<()> {
    let frame_time = Duration::from_secs_f64(1.0 / FPS as f64);

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

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

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

        let loop_start = Instant::now();

        let write_result = {
            let dmx_buffer = state.lock().unwrap().data;
            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::Sync) => continue 'main,
                Err(_) => continue, // Eventually the MCU will send a "Sync" again
            }
        }

        let loop_time = loop_start.elapsed();

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