use std::io::Write;
use tool_helper::{*, bits::BitRange, raw::RawConversion};

macro_rules! set_member_value {
    ($dst:expr, $color:ident, $shift:expr, $bits:ident) => {
        paste::item! {
            tool_helper::bits::[< set_value_ $bits >]($dst, ($color >> $shift) as $bits, &Self::[< $color:upper _BIT_RANGE >])
        }
    };
}

macro_rules! make_member_getter_setter {
    ($color:ident, $shift:expr, $bits:ident) => {
        paste::item! {
            pub fn [< set_ $color >](&mut self, $color: u8) {
                self.value = set_member_value!(self.value, $color, $shift, $bits);
            }

            pub fn [< get_ $color >](&self) -> u8 {
                (bits::[< get_value_ $bits >](self.value, &Self::[< $color:upper _BIT_RANGE >]) as u8) << $shift
            }
        }
    };
}

pub(crate) use set_member_value;

use super::args;

#[repr(packed(1))]
#[allow(dead_code)]
pub struct Color {
    value: u16
}

#[allow(dead_code)]
impl Color {
    const COLOR_SHIFT:     u16      = 3;
    const STP_BIT_RANGE:   BitRange = BitRange::from_to(15, 15);
    const RED_BIT_RANGE:   BitRange = BitRange::from_to(0, 4);
    const GREEN_BIT_RANGE: BitRange = BitRange::from_to(5, 9);
    const BLUE_BIT_RANGE:  BitRange = BitRange::from_to(10, 14);

    const PIXEL3_4_BIT_RANGE: BitRange = BitRange::from_to(12, 15);
    const PIXEL2_4_BIT_RANGE: BitRange = BitRange::from_to(8,  11);
    const PIXEL1_4_BIT_RANGE: BitRange = BitRange::from_to(4,   7);
    const PIXEL0_4_BIT_RANGE: BitRange = BitRange::from_to(0,   3);

    const PIXEL1_8_BIT_RANGE: BitRange = BitRange::from_to(8, 15);
    const PIXEL0_8_BIT_RANGE: BitRange = BitRange::from_to(0,  7);

    pub const fn transparent() -> Color {
        Color::new(0, 0, 0, 0)
    }

    pub const fn non_transparent(red: u8, green: u8, blue: u8) -> Color {
        if red == 0 && green == 0 && blue == 0 {
            Color::black()
        }

        else {
            Color::new(0, red, green, blue)
        }
    }

    pub const fn semi_transparent(mut red: u8, mut green: u8, mut blue: u8) -> Color {
        if red == 0 {
            red = 1;
        }

        if green == 0 {
            green = 1;
        }

        if blue == 0 {
            blue = 1;
        }

        Color::new(1, red, green, blue)
    }

    pub const fn black() -> Color {
        Color::new(1, 0, 0, 0)
    }

    pub const fn new_4bit_entry(pixel0_4: u8, pixel1_4: u8, pixel2_4: u8, pixel3_4: u8) -> Color {
        let value = set_member_value!(set_member_value!(set_member_value!(set_member_value!(0, 
            pixel0_4, 0, u16),
            pixel1_4, 0, u16),
            pixel2_4, 0, u16),
            pixel3_4, 0, u16);

        Color{value}
    }

    pub const fn new_8bit_entry(pixel0_8: u8, pixel1_8: u8) -> Color {
        let value = set_member_value!(set_member_value!(0, 
            pixel0_8, 0, u16),
            pixel1_8, 0, u16);

        Color{value}
    }

    const fn new(stp: u8, red: u8, green: u8, blue: u8) -> Color {
        let value = set_member_value!(set_member_value!(set_member_value!(set_member_value!(0, 
            stp,   0,                 u16), 
            red,   Self::COLOR_SHIFT, u16),
            green, Self::COLOR_SHIFT, u16),
            blue,  Self::COLOR_SHIFT, u16);

        Color{value}
    }

    make_member_getter_setter!(stp,   0,                 u16);
    make_member_getter_setter!(red,   Self::COLOR_SHIFT, u16);
    make_member_getter_setter!(green, Self::COLOR_SHIFT, u16);
    make_member_getter_setter!(blue,  Self::COLOR_SHIFT, u16);
}

impl RawConversion<2> for Color {
    fn convert_to_raw(&self) -> [u8; 2] {
        self.value.to_le_bytes()
    }
}

pub struct Rect {
    pub x:      u16,
    pub y:      u16,
    pub width:  u16,
    pub height: u16,
}

impl Rect {
    pub fn new(x: u16, y: u16, width: u16, height: u16) -> Rect {
        Rect{x, y, width, height}
    }
}

pub trait HeaderEncoder {
    fn encode_settings(&mut self, color_type: args::ColorType, tex_rect: Rect, clut_rect: Rect) -> Result<(), Error>;
    fn write_header(&self, output: &mut dyn Write)                                                                                 -> Result<usize, Error>;
    fn write_clut_header(&self, output: &mut dyn Write)                                                                            -> Result<usize, Error>;
    fn write_pixel_header(&self, output: &mut dyn Write)                                                                           -> Result<usize, Error>;
}

pub trait PSXImageConverter: std::iter::Iterator<Item = Color> {
    fn width(&self)  -> u16;
    fn height(&self) -> u16;

    fn get_palette(&self) -> Option<&Vec<Color>>;
}