use tool_helper::Error;
use super::types::{Color, PSXImageConverter};

pub enum OutputType {
    FourBit,
    EightBit,
}

enum IndexPerByte {
    OneIndex,
    TwoIndices,
}

pub struct IndexedImage {
    pub palette:     Vec<Color>,
        raw_data:    std::vec::IntoIter<u8>,
        index_byte:  IndexPerByte,
        next_func:   fn(&mut Self) -> Option<Color>,
        width:       u16,
        height:      u16,
}

impl IndexedImage {
    pub fn new<R: std::io::Read>(mut reader: png::Reader<R>, output_type: OutputType) -> Result<IndexedImage, Error> {
        let action_name = "Creating IndexedImage";

        let mut buffer = vec![0; reader.output_buffer_size()];
        match reader.next_frame(&mut buffer) {
            Ok(info) => {
                let width     = info.width  as u16;
                let height    = info.height as u16;
                let index_byte = {                
                    match info.bit_depth {
                        png::BitDepth::Four  => IndexPerByte::TwoIndices,
                        png::BitDepth::Eight => IndexPerByte::OneIndex,
                        _ => {
                            return Err(Error::from_text(format!("{}: Only 4 and 8bit color depth are supported", action_name)));
                        }
                    }  
                };
                
                if info.color_type != png::ColorType::Indexed {
                    return Err(Error::from_text(format!("{}: PNG file must be indexed", action_name)));
                }

                else {
                    Ok(IndexedImage{palette: Self::make_palette(reader.info()), raw_data: buffer.into_iter(), index_byte, next_func: {
                        match output_type {
                            OutputType::FourBit  => Self::next_four_bit,
                            OutputType::EightBit => Self::next_eight_bit,
                        }
                    }, width, height})
                }
            }

            Err(err) => {
                Err(Error::from_text(format!("{}: {}", action_name, err.to_string())))
            }
        }
    }

    fn make_palette(png_info: &png::Info) -> Vec<Color> {
        fn make_color(iter: &mut dyn std::iter::Iterator<Item = &u8>) -> Option<Color> {
            let r = iter.next()?;
            let g = iter.next()?;
            let b = iter.next()?;
    
            Some(Color::non_transparent(*r, *g, *b))
        }

        let mut new_palette = Vec::new();

        if let Some(palette) = &png_info.palette {
            let mut iter = palette.iter();

            while let Some(color) = make_color(&mut iter) {
                new_palette.push(color);
            }
        }
        new_palette
    }

    fn split_into_two_indices(index: u8) -> (u8, u8) {
        (index >> 4, index & 0xF)
    }

    fn next_four_bit(&mut self) -> Option<Color> {
        let (pixel0, pixel1, pixel2, pixel3) = {
            match self.index_byte {
                IndexPerByte::OneIndex => {
                    (self.raw_data.next()?, self.raw_data.next()?, self.raw_data.next()?, self.raw_data.next()?)
                }
                IndexPerByte::TwoIndices => {
                    let (pixel0, pixel1) = Self::split_into_two_indices(self.raw_data.next()?);
                    let (pixel2, pixel3) = Self::split_into_two_indices(self.raw_data.next()?);

                    (pixel0, pixel1, pixel2, pixel3)
                },
            }
        };

        Some(Color::new_4bit_entry(pixel0, pixel1, pixel2, pixel3))
    }

    fn next_eight_bit(&mut self) -> Option<Color> {
        let (pixel0, pixel1) = {
            match self.index_byte {
                IndexPerByte::OneIndex   => {
                    (self.raw_data.next()?, self.raw_data.next()?)
                },
                IndexPerByte::TwoIndices => {
                    Self::split_into_two_indices(self.raw_data.next()?)
                }
            }
        };

        Some(Color::new_8bit_entry(pixel0, pixel1))
    }
}

impl PSXImageConverter for IndexedImage {
    fn width(&self) -> u16 {
        self.width
    }

    fn height(&self) -> u16 {
        self.height
    }

    fn get_palette(&self) -> Option<&Vec<Color>> {
        Some(&self.palette)
    }
}

impl std::iter::Iterator for IndexedImage {
    type Item = Color;

    fn next(&mut self) -> Option<Self::Item> {
        (self.next_func)(self)
    }
}