use core::cmp::max;

use figments::{liber8tion::{interpolate::{Fract8, ease_in_out_quad}, noise::inoise8, trig::Trig8}, prelude::*};
use num_traits::WrappingAdd;
use rgb::Rgba;

use crate::graphics::display::{SegmentSpace, Uniforms};

#[derive(Clone, Copy, Default)]
pub struct Movement {
    reverse: bool
}

impl Movement {
    pub fn reversed(self) -> Self {
        Self {
            reverse: !self.reverse
        }
    }
}

impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Movement {
    fn draw(&self, surface_coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
        let offset = if self.reverse {
            uniforms.frame.wrapping_add(surface_coords.x)
        } else {
            uniforms.frame.wrapping_sub(surface_coords.x)
        };
        let idx = offset.sin8().wrapping_add(&Fract8::from_raw(uniforms.primary_color.hue));
        Rgba::new(idx.to_raw(), idx.to_raw().wrapping_mul(2), idx.to_raw() / 2, 128)
    }
}

#[derive(Clone, Copy, Default)]
pub struct Background {
    color: Option<Rgb<u8>>
}

impl Background {
    pub fn from_color(color: Rgb<u8>) -> Self {
        Self {
            color: Some(color)
        }
    }
}

impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Background {
    fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
        let noise_x = (uniforms.frame % 255).sin8().to_raw() as i16;
        let noise_y = (uniforms.frame % 255).cos8().to_raw() as i16;
        let brightness = inoise8(noise_x.wrapping_add(coords.x as i16), noise_y.wrapping_add(coords.y as i16));
        let saturation = inoise8(noise_y.wrapping_add(coords.y as i16), noise_x.wrapping_add(coords.x as i16));
        let rgb: Rgb<u8> = match self.color {
            None => Hsv::new(uniforms.primary_color.hue, max(128, saturation.to_raw()), brightness.to_raw()).into(),
            Some(c) => c
        };

        Rgba::new(rgb.r, rgb.g, rgb.b, 255)
    }
}

#[derive(Clone, Copy, Default)]
pub struct Tail {}
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Tail {
    fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
        let hue_offset: u8 = 32u8 * Fract8::from_raw(uniforms.frame.wrapping_sub(coords.x) as u8);
        let value = max(30, inoise8(coords.x.wrapping_add(uniforms.frame) as i16, coords.y.wrapping_add(uniforms.frame) as i16).to_raw());
        Hsv::new(uniforms.primary_color.hue.wrapping_sub(hue_offset), max(210, uniforms.frame.wrapping_add(coords.x).sin8().to_raw()), value).into()
    }
}

#[derive(Clone, Copy, Default)]
pub struct Brakelight {}

impl Brakelight {
    pub const fn end() -> usize {
        87
    }

    pub const fn length() -> usize {
        50
    }

    pub const fn safety_length() -> usize {
        15
    }

    pub const fn rectangle() -> Rectangle<SegmentSpace> {
        Rectangle::new_from_coordinates(Self::end() - Self::length(), 5, Self::end(), 5)
    }

}

impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Brakelight {

    fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
        let distance_from_end = Self::end() - coords.x;
        
        if distance_from_end < Self::safety_length() {
            Rgba::new(max(128, uniforms.frame.wrapping_sub(coords.x).sin8().to_raw()), 0, 0, 255)
        } else {
            let pct = (distance_from_end as f32 / Self::length() as f32) * 255f32;
            Rgba::new(max(100, ease_in_out_quad(Fract8::from_raw(pct as u8).wrapping_add(&Fract8::from_raw(uniforms.frame as u8))).to_raw()), 0, 0, ease_in_out_quad(Fract8::from_raw(255 - pct as u8)).to_raw())
        }
    }
}

#[derive(Clone, Copy, Default)]
pub struct Headlight {}
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Headlight {
    fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
        Hsv::new(0, 0, max(130, ease_in_out_quad(uniforms.frame.wrapping_sub(coords.x).sin8()).to_raw())).into()
    }
}

#[derive(Clone, Copy, Default)]
pub struct Panel {}
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Panel {
    fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
        let noise_offset = max(180, inoise8(coords.x.wrapping_add(uniforms.frame) as i16, coords.y.wrapping_add(uniforms.frame) as i16).to_raw());
        let pct = (coords.x as f32 / 18f32) * 255f32;
        let shift = match coords.y {
            1..=2 => 106, // 150 degrees
            3..=4 => 148, // 210 degrees
            _ => 0
        };
        Hsv::new(uniforms.primary_color.hue.wrapping_add(shift), noise_offset, max(100, pct.sin8().wrapping_add(&Fract8::from_raw(uniforms.frame as u8)).to_raw())).into()
    }
}

#[derive(Clone, Copy, Default)]
pub struct Thinking {}

impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Thinking {
    fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
        //let noise_x = sin8(sin8((frame % 255) as u8).wrapping_add(coords.x));
        //let noise_y = cos8(cos8((frame % 255) as u8).wrapping_add(coords.y));
        let offset_x = uniforms.frame.wrapping_add(coords.x).sin8();
        let offset_y = uniforms.frame.wrapping_add(coords.y).cos8();
        let noise_x = offset_x / 2;
        let noise_y = offset_y / 2;
        //let noise_x = coords.x.wrapping_add(offset_x);
        //let noise_y = coords.y.wrapping_add(offset_y);
        Hsv::new(
            inoise8(offset_x.to_raw() as i16, offset_y.to_raw() as i16).to_raw(),
            128_u8.saturating_add(inoise8(noise_y.to_raw().into(), noise_x.to_raw().into()).to_raw()),
            255
        ).into()
    }
}