#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Header {
    pub width: u32,
    pub height: u32,
    pub channels: Channels,
    pub colorspace: Colorspace,
}


#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Colorspace {
    Srgb,
    Linear
}

impl Colorspace {
    pub fn from_repr (i: u8) -> Option<Self> {
        match i {
            0 => Some(Self::Srgb),
            1 => Some(Self::Linear),
            _ => None
        }
    }

    pub fn to_repr (&self) -> u8 {
        match self {
            Self::Srgb => 0,
            Self::Linear => 1,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Channels {
    RGB,
    RGBA
}

impl Channels {
    pub fn num (&self) -> usize {
        match self {
            Self::RGBA => 4,
            Self::RGB => 3,
        }
    }

    pub fn ops_color (&self, bytes: &[u8]) -> Option<Ops> {
        match self {
            Channels::RGB =>  Some(Ops::Rgb(bytes.try_into().ok()?)),
            Channels::RGBA =>  Some(Ops::Rgba(bytes.try_into().ok()?)),
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Ops {
    Index (usize),
    Run (u8),
    Luma ([i8; 3]),
    Diff ([i8; 3]),
    Rgb (RgbColor),
    Rgba (RgbaColor),
    Nop
}

type RgbColor = [u8; 3];
type RgbaColor = [u8; 4];


pub const MAGIC: [u8; 4] = [b'q', b'o', b'i', b'f'];

pub fn hash ([r, g, b, a]: [u8; 4]) -> usize {
     (r as usize * 3 + g as usize * 5 + b as usize * 7 + a as usize * 11) % 64
}

pub fn calc_diff ([r0, g0, b0, _]: [i16; 4], [r1, g1, b1, _]: [i16; 4]) -> [i16; 3] {
    [diff(r0, r1), diff(g0, g1), diff(b0, b1)]
}

pub fn diff_ops (left: [u8; 4], right: [u8; 4]) -> Option<Ops> {
    if left[3] != right[3] { return None; }
    let left: [i16; 4] = left.map(i16::from);
    let right: [i16; 4] = right.map(i16::from);

    let diff = calc_diff(left, right);

    if diff.iter().all(|d| (-2..2).contains(d)) {
        Some(Ops::Diff(diff.map(|e| e as i8)))
    } else if let Some(luma) = new_luma(diff) {
        Some(luma)
    } else { None }
}

pub fn new_luma ([r, g, b]: [i16; 3]) -> Option<Ops> {
    let drdg = r - g;
    let dbdg = b - g;
    if (-32..32).contains(&g) && (-8..8).contains(&drdg) && (-8..8).contains(&dbdg) {
        return Some(Ops::Luma([drdg as i8, g as i8, dbdg as i8]));
    }
    return None;
}

fn diff (lhs: i16, rhs: i16) -> i16 {
    let mut diff = lhs - rhs;
    if diff.abs() > 128 {
        diff = 256 - rhs + lhs + 1 * match diff.is_negative() {
            true => 1,
            false => -1
        };
    }
    diff
}

pub fn cast_with_bias (num: i8, bias: i8) -> u8 {
    (num + bias) as u8
}
