use crate::colors::{BitDepth, ColorType};
use crate::headers::IhdrData;
use crate::png::PngImage;
use bit_vec::BitVec;

const ONE_BIT_PERMUTATIONS: [u8; 2] = [0b0000_0000, 0b1111_1111];
const TWO_BIT_PERMUTATIONS: [u8; 4] = [0b0000_0000, 0b0101_0101, 0b1010_1010, 0b1111_1111];
const FOUR_BIT_PERMUTATIONS: [u8; 16] = [
    0b0000_0000,
    0b0001_0001,
    0b0010_0010,
    0b0011_0011,
    0b0100_0100,
    0b0101_0101,
    0b0110_0110,
    0b0111_0111,
    0b1000_1000,
    0b1001_1001,
    0b1010_1010,
    0b1011_1011,
    0b1100_1100,
    0b1101_1101,
    0b1110_1110,
    0b1111_1111,
];

/// Attempt to reduce the bit depth of the image
/// Returns true if the bit depth was reduced, false otherwise
#[must_use]
pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage> {
    if png.ihdr.bit_depth != BitDepth::Sixteen {
        if png.ihdr.color_type == ColorType::Indexed || png.ihdr.color_type == ColorType::Grayscale
        {
            return reduce_bit_depth_8_or_less(png, minimum_bits);
        }
        return None;
    }

    // Reduce from 16 to 8 bits per channel per pixel
    let mut reduced = Vec::with_capacity(
        (png.ihdr.width * png.ihdr.height * u32::from(png.channels_per_pixel()) + png.ihdr.height)
            as usize,
    );
    let mut high_byte = 0;

    for line in png.scan_lines() {
        reduced.push(line.filter);
        for (i, &byte) in line.data.iter().enumerate() {
            if i % 2 == 0 {
                // High byte
                high_byte = byte;
            } else {
                // Low byte
                if high_byte != byte {
                    // Can't reduce, exit early
                    return None;
                }
                reduced.push(byte);
            }
        }
    }

    Some(PngImage {
        data: reduced,
        ihdr: IhdrData {
            bit_depth: BitDepth::Eight,
            ..png.ihdr
        },
        palette: None,
        transparency_pixel: png.transparency_pixel.clone(),
        aux_headers: png.aux_headers.clone(),
    })
}

#[must_use]
pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Option<PngImage> {
    assert!(minimum_bits >= 1 && minimum_bits < 8);
    let mut reduced = BitVec::with_capacity(png.data.len() * 8);
    let bit_depth: usize = png.ihdr.bit_depth.as_u8() as usize;
    if minimum_bits >= bit_depth {
        return None;
    }
    for line in png.scan_lines() {
        if png.ihdr.color_type == ColorType::Indexed {
            let line_max = line
                .data
                .iter()
                .map(|&byte| match png.ihdr.bit_depth {
                    BitDepth::Two => (byte & 0x3)
                        .max((byte >> 2) & 0x3)
                        .max((byte >> 4) & 0x3)
                        .max(byte >> 6),
                    BitDepth::Four => (byte & 0xF).max(byte >> 4),
                    _ => byte,
                })
                .max()
                .unwrap_or(0);
            let required_bits = match line_max {
                x if x > 0x0F => 8,
                x if x > 0x03 => 4,
                x if x > 0x01 => 2,
                _ => 1,
            };
            if required_bits > minimum_bits {
                minimum_bits = required_bits;
                if minimum_bits >= bit_depth {
                    // Not reducable
                    return None;
                }
            }
        } else {
            let bit_vec = BitVec::from_bytes(&line.data);
            for byte in bit_vec.to_bytes() {
                while minimum_bits < bit_depth {
                    let permutations: &[u8] = if minimum_bits == 1 {
                        &ONE_BIT_PERMUTATIONS
                    } else if minimum_bits == 2 {
                        &TWO_BIT_PERMUTATIONS
                    } else if minimum_bits == 4 {
                        &FOUR_BIT_PERMUTATIONS
                    } else {
                        return None;
                    };
                    if permutations.iter().any(|perm| *perm == byte) {
                        break;
                    } else {
                        minimum_bits <<= 1;
                    }
                }
            }
        }
    }

    for line in png.scan_lines() {
        reduced.extend(BitVec::from_bytes(&[line.filter]));
        let bit_vec = BitVec::from_bytes(&line.data);
        for (i, bit) in bit_vec.iter().enumerate() {
            let bit_index = bit_depth - (i % bit_depth);
            if bit_index <= minimum_bits {
                reduced.push(bit);
            }
        }
        // Pad end of line to get 8 bits per byte
        while reduced.len() % 8 != 0 {
            reduced.push(false);
        }
    }

    Some(PngImage {
        data: reduced.to_bytes(),
        ihdr: IhdrData {
            bit_depth: BitDepth::from_u8(minimum_bits as u8),
            ..png.ihdr
        },
        aux_headers: png.aux_headers.clone(),
        palette: png.palette.clone(),
        transparency_pixel: png.transparency_pixel.clone(),
    })
}
