use std::f64::consts::{FRAC_PI_2, PI};

use num::traits::float::Float;

pub fn rescale<T: Float>(x: T, from: (T, T), to: (T, T)) -> T {
    let (a, b) = from;
    let (c, d) = to;

    c + (d - c) * (x - a) / (b - a)
}

pub enum Brightness {
    Off,
    Dimmer(f64), // 0 to 1
    Strobe(f64), // 0 to 1
    Switch,      // No idea what this does
}

pub struct MovingHead {
    pub start_address: usize,

    pub pan: f64,  // -3pi/2 to 3pi/2
    pub tilt: f64, // -pi/2 to pi/2
    pub brightness: Brightness,
    pub rgbw: (u8, u8, u8, u8), // RGBW
    pub speed: f64,             // 0 to 1, mapping to (255 to 0)

    pub reset: bool,
}

pub trait DMXFixture {
    fn render(&self, dst: &mut [u8]);
}

impl DMXFixture for MovingHead {
    fn render(&self, dst: &mut [u8]) {
        // TODO use pan_fine and tilt_fine for more accuracy

        let pan = rescale(self.pan, (-1.5 * PI, 1.5 * PI), (255.0, 0.0)) as u8;
        let pan_fine = 0;

        let tilt = rescale(self.tilt, (-1.0 * FRAC_PI_2, FRAC_PI_2), (0.0, 255.0)) as u8;
        let tilt_fine = 0;

        let dimmer = match self.brightness {
            Brightness::Off => 0,
            Brightness::Dimmer(dimmer) => rescale(dimmer, (0.0, 1.0), (8.0, 134.0)) as u8,
            Brightness::Strobe(strobe) => rescale(strobe, (0.0, 1.0), (135.0, 239.0)) as u8,
            Brightness::Switch => 255,
        };

        let (r, g, b, w) = self.rgbw;

        let speed = rescale(self.speed, (0.0, 1.0), (255.0, 0.0)) as u8;

        let reset = if self.reset { 175 } else { 0 };

        let offset = self.start_address - 1;

        let channels = [
            pan, pan_fine, tilt, tilt_fine, speed, dimmer, r, g, b, w, 0, 0, 0, reset,
        ];

        dst[offset..offset + channels.len()].copy_from_slice(&channels);
    }
}
