pub mod args;
pub mod color_clut;
pub mod color_full16;
pub mod reduced_tim;
pub mod tim;
pub mod types;

use args::{ColorType, ClutAlignment, Point};
use color_clut::{IndexedImage, OutputType};
use color_full16::{RgbaImage, RgbImage};
use types::{Color as PSXColor, HeaderEncoder, PSXImageConverter, Rect};
use image::{DynamicImage, io::Reader as ImageReader};
use std::io::{Cursor, Write};
use tool_helper::{Error, Input};

fn modify_palette(mut image: IndexedImage, clut_align: ClutAlignment, semi_transparent: bool, transparent_palette: bool) -> IndexedImage {
    if semi_transparent {
        for color in image.palette.iter_mut() {
            *color = PSXColor::semi_transparent(color.get_red(), color.get_green(), color.get_blue());
        }
    }

    if transparent_palette {
        if clut_align == ClutAlignment::Block {
            for color in image.palette.iter_mut().step_by(16) {
                *color = PSXColor::transparent();
            }
        }

        else {
            if let Some(first_color) = image.palette.get_mut(0) {
                *first_color = PSXColor::transparent();
            }
        }
    }
    image
}

fn encode<T: PSXImageConverter>(header_conv: &mut dyn HeaderEncoder, image: T, tex_pos: Point, clut_pos: Point, color_depth: ColorType, clut_align: ClutAlignment, output: &mut dyn Write) -> Result<(), Error> {
    let (width, height) = {
        fn return_error(clut_type: u32, div: u32, width: u16, height: u16) -> Result<(u16, u16), Error> {
            return Err(Error::from_callback(|| {format!("CLUT {} images require a width divideable by {} (found width: {}/{}={}, height: {})", clut_type, div, width,  div, (width as f32/div as f32), height)}));
        }

        let width  = image.width();
        let height = image.height();
        match color_depth {
            ColorType::Clut4  => {
                if width & 3 == 0 {
                    Ok((width/4, height))
                }

                else {
                    return_error(4, 4, width, height) 
                }
            },
            ColorType::Clut8  => {
                if width & 1 == 0 {
                    Ok((width/2, height))
                }

                else {
                    return_error(8, 2, width, height)
                }
            },
            ColorType::Full16 => {
                Ok((width, height))
            }
        }
    }?;
    let palette                 = image.get_palette();
    let (pal_width, pal_height) = {
        if let Some(palette) = &palette {
            let pal_length_adjusted = {
                let pal_length = palette.len();
                if pal_length <= 16 {
                    16u16
                }
                
                else {
                    256u16
                }
            };
            match clut_align {
                ClutAlignment::None   |
                ClutAlignment::Linear => (pal_length_adjusted,        1u16),
                ClutAlignment::Block  => (16u16, pal_length_adjusted/16u16),
            }
        }

        else {
            (0u16, 0u16)
        }
    };

    header_conv.encode_settings(color_depth, Rect::new(tex_pos.x, tex_pos.y, width, height), Rect::new(clut_pos.x, clut_pos.y, pal_width, pal_height))?;
    
    header_conv.write_header(output)?;
    header_conv.write_clut_header(output)?;
    if let Some(palette) = palette {
        let mut color_count = pal_width*pal_height;
        for color in palette {
            tool_helper::raw::write_raw(output, color)?;
            color_count -= 1;
        }

        while color_count > 0 {
            tool_helper::raw::write_raw(output, &PSXColor::black())?;
            color_count -= 1;
        }
    }

    header_conv.write_pixel_header(output)?;
    for color in image {
        tool_helper::raw::write_raw(output, &color)?;
    }

    Ok(())
}

fn convert_full16(header_conv: &mut dyn HeaderEncoder, input: Input, output: &mut dyn Write, tex_pos: Point) -> Result<(), Error> {
    match ImageReader::new(Cursor::new(tool_helper::input_to_vec(input)?)).with_guessed_format()?.decode() {
        Ok(image) => {
            match image {
                DynamicImage::ImageRgb8(image)  => encode(header_conv, RgbImage::new(image),  tex_pos, Point::default(), ColorType::Full16, ClutAlignment::None, output),
                DynamicImage::ImageRgba8(image) => encode(header_conv, RgbaImage::new(image), tex_pos, Point::default(), ColorType::Full16, ClutAlignment::None, output),

                _ => Err(Error::from_str("Only RGB and RGBA images are supported for 16bit encoding"))
            }
        },
        Err(error) => Err(Error::from_error(error))
    }
}

fn convert_palette_based(header_conv: &mut dyn HeaderEncoder, input: Input, output: &mut dyn Write, tex_pos: Point, clut_pos: Point, color_type: ColorType, clut_align: ClutAlignment, semi_transparent: bool, transparent_palette: bool) -> Result<(), Error> {
    match png::Decoder::new(input).read_info() {
        Ok(reader) => {
            let output_type = {
                match color_type {
                    ColorType::Clut4 => OutputType::FourBit,
                    ColorType::Clut8 => OutputType::EightBit,
                    _                => return Err(Error::from_str("ColorType not supported"))
                }
            };
            
            encode(header_conv, modify_palette(IndexedImage::new(reader, output_type)?, clut_align, semi_transparent, transparent_palette), tex_pos, clut_pos, color_type, clut_align, output)
        },
        Err(error) => Err(Error::from_error(error))
    }
}