/*
 * Copyright (c) 2023.
 *
 * This software is free software;
 *
 * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license
 */

//! Decode Decoder markers/segments
//!
//! This file deals with decoding header information in a jpeg file
//!
use alloc::format;
use alloc::string::ToString;
use alloc::vec::Vec;

use zune_core::bytestream::ZByteReaderTrait;
use zune_core::colorspace::ColorSpace;
use zune_core::log::{debug, trace, warn};

use crate::components::Components;
use crate::decoder::{GainMapInfo, ICCChunk, JpegDecoder, MAX_COMPONENTS};
use crate::errors::DecodeErrors;
use crate::huffman::HuffmanTable;
use crate::misc::{SOFMarkers, UN_ZIGZAG};

///**B.2.4.2 Huffman table-specification syntax**
#[allow(clippy::similar_names, clippy::cast_sign_loss)]
pub(crate) fn parse_huffman<T: ZByteReaderTrait>(
    decoder: &mut JpegDecoder<T>
) -> Result<(), DecodeErrors>
where
{
    // Read the length of the Huffman table
    let mut dht_length = i32::from(decoder.stream.get_u16_be_err()?.checked_sub(2).ok_or(
        DecodeErrors::FormatStatic("Invalid Huffman length in image")
    )?);

    while dht_length > 16 {
        // HT information
        let ht_info = decoder.stream.read_u8_err()?;
        // third bit indicates whether the huffman encoding is DC or AC type
        let dc_or_ac = (ht_info >> 4) & 0xF;
        // Indicate the position of this table, should be less than 4;
        let index = (ht_info & 0xF) as usize;
        // read the number of symbols
        let mut num_symbols: [u8; 17] = [0; 17];

        if index >= MAX_COMPONENTS {
            return Err(DecodeErrors::HuffmanDecode(format!(
                "Invalid DHT index {index}, expected between 0 and 3"
            )));
        }

        if dc_or_ac > 1 {
            return Err(DecodeErrors::HuffmanDecode(format!(
                "Invalid DHT position {dc_or_ac}, should be 0 or 1"
            )));
        }

        decoder.stream.read_exact_bytes(&mut num_symbols[1..17])?;

        dht_length -= 1 + 16;

        let symbols_sum: i32 = num_symbols.iter().map(|f| i32::from(*f)).sum();

        // The sum of the number of symbols cannot be greater than 256;
        if symbols_sum > 256 {
            return Err(DecodeErrors::FormatStatic(
                "Encountered Huffman table with excessive length in DHT"
            ));
        }
        if symbols_sum > dht_length {
            return Err(DecodeErrors::HuffmanDecode(format!(
                "Excessive Huffman table of length {symbols_sum} found when header length is {dht_length}"
            )));
        }
        dht_length -= symbols_sum;
        // A table containing symbols in increasing code length
        let mut symbols = [0; 256];

        decoder
            .stream
            .read_exact_bytes(&mut symbols[0..(symbols_sum as usize)])?;
        // store
        match dc_or_ac {
            0 => {
                decoder.dc_huffman_tables[index] = Some(HuffmanTable::new(
                    &num_symbols,
                    symbols,
                    true,
                    decoder.is_progressive
                )?);
            }
            _ => {
                decoder.ac_huffman_tables[index] = Some(HuffmanTable::new(
                    &num_symbols,
                    symbols,
                    false,
                    decoder.is_progressive
                )?);
            }
        }
    }

    if dht_length > 0 {
        return Err(DecodeErrors::FormatStatic("Bogus Huffman table definition"));
    }

    Ok(())
}

///**B.2.4.1 Quantization table-specification syntax**
#[allow(clippy::cast_possible_truncation, clippy::needless_range_loop)]
pub(crate) fn parse_dqt<T: ZByteReaderTrait>(img: &mut JpegDecoder<T>) -> Result<(), DecodeErrors> {
    // read length
    let mut qt_length =
        img.stream
            .get_u16_be_err()?
            .checked_sub(2)
            .ok_or(DecodeErrors::FormatStatic(
                "Invalid DQT length. Length should be greater than 2"
            ))?;
    // A single DQT header may have multiple QT's
    while qt_length > 0 {
        let qt_info = img.stream.read_u8_err()?;
        // 0 = 8 bit otherwise 16 bit dqt
        let precision = (qt_info >> 4) as usize;
        // last 4 bits give us position
        let table_position = (qt_info & 0x0f) as usize;
        let precision_value = 64 * (precision + 1);

        if (precision_value + 1) as u16 > qt_length {
            return Err(DecodeErrors::DqtError(format!("Invalid QT table bytes left :{}. Too small to construct a valid qt table which should be {} long", qt_length, precision_value + 1)));
        }

        let dct_table = match precision {
            0 => {
                let mut qt_values = [0; 64];

                img.stream.read_exact_bytes(&mut qt_values)?;

                qt_length -= (precision_value as u16) + 1 /*QT BIT*/;
                // carry out un zig-zag here
                un_zig_zag(&qt_values)
            }
            1 => {
                // 16 bit quantization tables
                let mut qt_values = [0_u16; 64];

                for i in 0..64 {
                    qt_values[i] = img.stream.get_u16_be_err()?;
                }
                qt_length -= (precision_value as u16) + 1;

                un_zig_zag(&qt_values)
            }
            _ => {
                return Err(DecodeErrors::DqtError(format!(
                    "Expected QT precision value of either 0 or 1, found {precision:?}"
                )));
            }
        };

        if table_position >= MAX_COMPONENTS {
            return Err(DecodeErrors::DqtError(format!(
                "Too large table position for QT :{table_position}, expected between 0 and 3"
            )));
        }

        trace!("Assigning qt table {table_position} with precision {precision}");
        img.qt_tables[table_position] = Some(dct_table);
    }

    return Ok(());
}

/// Section:`B.2.2 Frame header syntax`

pub(crate) fn parse_start_of_frame<T: ZByteReaderTrait>(
    sof: SOFMarkers, img: &mut JpegDecoder<T>
) -> Result<(), DecodeErrors> {
    if img.seen_sof {
        return Err(DecodeErrors::SofError(
            "Two Start of Frame Markers".to_string()
        ));
    }
    // Get length of the frame header
    let length = img.stream.get_u16_be_err()?;
    // usually 8, but can be 12 and 16, we currently support only 8
    // so sorry about that 12 bit images
    let dt_precision = img.stream.read_u8_err()?;

    if dt_precision != 8 {
        return Err(DecodeErrors::SofError(format!(
            "The library can only parse 8-bit images, the image has {dt_precision} bits of precision"
        )));
    }

    img.info.set_density(dt_precision);

    // read  and set the image height.
    let img_height = img.stream.get_u16_be_err()?;
    img.info.set_height(img_height);

    // read and set the image width
    let img_width = img.stream.get_u16_be_err()?;
    img.info.set_width(img_width);

    trace!("Image width  :{}", img_width);
    trace!("Image height :{}", img_height);

    if usize::from(img_width) > img.options.max_width() {
        return Err(DecodeErrors::Format(format!("Image width {} greater than width limit {}. If use `set_limits` if you want to support huge images", img_width, img.options.max_width())));
    }

    if usize::from(img_height) > img.options.max_height() {
        return Err(DecodeErrors::Format(format!("Image height {} greater than height limit {}. If use `set_limits` if you want to support huge images", img_height, img.options.max_height())));
    }

    // Check image width or height is zero
    if img_width == 0 || img_height == 0 {
        return Err(DecodeErrors::ZeroError);
    }

    // Number of components for the image.
    let num_components = img.stream.read_u8_err()?;

    if num_components == 0 {
        return Err(DecodeErrors::SofError(
            "Number of components cannot be zero.".to_string()
        ));
    }

    let expected = 8 + 3 * u16::from(num_components);
    // length should be equal to num components
    if length != expected {
        return Err(DecodeErrors::SofError(format!(
            "Length of start of frame differs from expected {expected},value is {length}"
        )));
    }

    trace!("Image components : {}", num_components);

    if num_components == 1 {
        // SOF sets the number of image components
        // and that to us translates to setting input and output
        // colorspaces to zero
        img.input_colorspace = ColorSpace::Luma;
        //img.options = img.options.jpeg_set_out_colorspace(ColorSpace::Luma);
        debug!("Overriding default colorspace set to Luma");
    }
    if num_components == 4 && img.input_colorspace == ColorSpace::YCbCr {
        trace!("Input image has 4 components, defaulting to CMYK colorspace");
        // https://entropymine.wordpress.com/2018/10/22/how-is-a-jpeg-images-color-type-determined/
        img.input_colorspace = ColorSpace::CMYK;
    }

    // set number of components
    img.info.components = num_components;

    let mut components = Vec::with_capacity(num_components as usize);
    let mut temp = [0; 3];

    for pos in 0..num_components {
        // read 3 bytes for each component
        img.stream.read_exact_bytes(&mut temp)?;

        // create a component.
        let component = Components::from(temp, pos)?;

        components.push(component);
    }
    img.seen_sof = true;

    img.info.set_sof_marker(sof);

    img.components = components;

    Ok(())
}

/// Parse a start of scan data
pub(crate) fn parse_sos<T: ZByteReaderTrait>(
    image: &mut JpegDecoder<T>
) -> Result<(), DecodeErrors> {
    // Scan header length
    let ls = usize::from(image.stream.get_u16_be_err()?);
    // Number of image components in scan
    let ns = image.stream.read_u8_err()?;

    let mut seen: [_; 5] = [-1; { MAX_COMPONENTS + 1 }];

    image.num_scans = ns;
    let smallest_size = 6 + 2 * usize::from(ns);

    if ls != smallest_size {
        return Err(DecodeErrors::SosError(format!(
            "Bad SOS length {ls},corrupt jpeg"
        )));
    }

    // Check number of components.
    if !(1..5).contains(&ns) {
        return Err(DecodeErrors::SosError(format!(
            "Invalid number of components in start of scan {ns}, expected in range 1..5"
        )));
    }

    if image.info.components == 0 {
        return Err(DecodeErrors::FormatStatic(
            "Error decoding SOF Marker, Number of components cannot be zero."
        ));
    }

    // consume spec parameters
    image.scan_subsampled = false;

    for i in 0..ns {
        let id = image.stream.read_u8_err()?;

        if seen.contains(&i32::from(id)) {
            return Err(DecodeErrors::SofError(format!(
                "Duplicate ID {id} seen twice in the same component"
            )));
        }

        seen[usize::from(i)] = i32::from(id);
        // DC and AC huffman table position
        // top 4 bits contain dc huffman destination table
        // lower four bits contain ac huffman destination table
        let y = image.stream.read_u8_err()?;

        let mut j = 0;

        while j < image.info.components {
            if image.components[j as usize].id == id {
                break;
            }

            j += 1;
        }

        if j == image.info.components {
            return Err(DecodeErrors::SofError(format!(
                "Invalid component id {}, expected one one of {:?}",
                id,
                image.components.iter().map(|c| c.id).collect::<Vec<_>>()
            )));
        }

        let component = &mut image.components[usize::from(j)];
        component.dc_huff_table = usize::from((y >> 4) & 0xF);
        component.ac_huff_table = usize::from(y & 0xF);
        image.z_order[i as usize] = j as usize;

        if component.vertical_sample != 1 || component.horizontal_sample != 1 {
            image.scan_subsampled = true;
        }

        trace!(
            "Assigned huffman tables {}/{} to component {j}, id={}",
            image.components[usize::from(j)].dc_huff_table,
            image.components[usize::from(j)].ac_huff_table,
            image.components[usize::from(j)].id,
        );
    }

    // Collect the component spec parameters
    // This is only needed for progressive images but I'll read
    // them in order to ensure they are correct according to the spec

    // Extract progressive information

    // https://www.w3.org/Graphics/JPEG/itu-t81.pdf
    // Page 42

    // Start of spectral / predictor selection. (between 0 and 63)
    image.spec_start = image.stream.read_u8_err()?;
    // End of spectral selection
    image.spec_end = image.stream.read_u8_err()?;

    let bit_approx = image.stream.read_u8_err()?;
    // successive approximation bit position high
    image.succ_high = bit_approx >> 4;

    if image.spec_end > 63 {
        return Err(DecodeErrors::SosError(format!(
            "Invalid Se parameter {}, range should be 0-63",
            image.spec_end
        )));
    }
    if image.spec_start > 63 {
        return Err(DecodeErrors::SosError(format!(
            "Invalid Ss parameter {}, range should be 0-63",
            image.spec_start
        )));
    }
    if image.succ_high > 13 {
        return Err(DecodeErrors::SosError(format!(
            "Invalid Ah parameter {}, range should be 0-13",
            image.succ_low
        )));
    }
    // successive approximation bit position low
    image.succ_low = bit_approx & 0xF;

    if image.succ_low > 13 {
        return Err(DecodeErrors::SosError(format!(
            "Invalid Al parameter {}, range should be 0-13",
            image.succ_low
        )));
    }
    // skip any bytes not read
    image.stream.skip(smallest_size.saturating_sub(ls))?;

    trace!(
        "Ss={}, Se={} Ah={} Al={}",
        image.spec_start,
        image.spec_end,
        image.succ_high,
        image.succ_low
    );

    Ok(())
}

/// Parse the APP13 (IPTC) segment.
pub(crate) fn parse_app13<T: ZByteReaderTrait>(
    decoder: &mut JpegDecoder<T>
) -> Result<(), DecodeErrors> {
    const IPTC_PREFIX: &[u8] = b"Photoshop 3.0\0";
    // skip length.
    let mut length = usize::from(decoder.stream.get_u16_be());

    if length < 2 {
        return Err(DecodeErrors::FormatStatic("Too small APP13 length"));
    }
    // length bytes.
    length -= 2;

    if length > IPTC_PREFIX.len() && decoder.stream.peek_at(0, IPTC_PREFIX.len())? == IPTC_PREFIX {
        // skip bytes we read above.
        decoder.stream.skip(IPTC_PREFIX.len())?;
        length -= IPTC_PREFIX.len();

        let iptc_bytes = decoder.stream.peek_at(0, length)?.to_vec();

        decoder.info.iptc_data = Some(iptc_bytes);
    }

    decoder.stream.skip(length)?;
    Ok(())
}

/// Parse Adobe App14 segment
pub(crate) fn parse_app14<T: ZByteReaderTrait>(
    decoder: &mut JpegDecoder<T>
) -> Result<(), DecodeErrors> {
    // skip length
    let mut length = usize::from(decoder.stream.get_u16_be());

    if length < 2 {
        return Err(DecodeErrors::FormatStatic("Too small APP14 length"));
    }

    if decoder.stream.peek_at(0, 5)? == b"Adobe" {
        if length < 14 {
            return Err(DecodeErrors::FormatStatic(
                "Too short of a length for App14 segment"
            ));
        }
        // move stream 6 bytes to remove adobe id
        decoder.stream.skip(6)?;
        // skip version, flags0 and flags1
        decoder.stream.skip(5)?;
        // get color transform
        let transform = decoder.stream.read_u8();
        // https://exiftool.org/TagNames/JPEG.html#Adobe
        match transform {
            0 => decoder.input_colorspace = ColorSpace::CMYK,
            1 => decoder.input_colorspace = ColorSpace::YCbCr,
            2 => decoder.input_colorspace = ColorSpace::YCCK,
            _ => {
                return Err(DecodeErrors::Format(format!(
                    "Unknown Adobe colorspace {transform}"
                )))
            }
        }
        // length   = 2
        // adobe id = 6
        // version =  5
        // transform = 1
        length = length.saturating_sub(14);
    } else {
        warn!("Not a valid Adobe APP14 Segment, skipping");
    }
    // skip any proceeding lengths.
    // we do not need them
    decoder.stream.skip(length)?;

    Ok(())
}

/// Parse the APP1 segment
///
/// This contains the exif tag
pub(crate) fn parse_app1<T: ZByteReaderTrait>(
    decoder: &mut JpegDecoder<T>
) -> Result<(), DecodeErrors> {
    const XMP_NAMESPACE_PREFIX: &[u8] = b"http://ns.adobe.com/xap/1.0/\0";

    // contains exif data
    let mut length = usize::from(decoder.stream.get_u16_be());

    if length < 2 {
        return Err(DecodeErrors::FormatStatic("Too small app1 length"));
    }
    // length bytes
    length -= 2;

    if length > 6 && decoder.stream.peek_at(0, 6)? == b"Exif\x00\x00" {
        trace!("Exif segment present");
        // skip bytes we read above
        decoder.stream.skip(6)?;
        length -= 6;

        let exif_bytes = decoder.stream.peek_at(0, length)?.to_vec();

        decoder.info.exif_data = Some(exif_bytes);
    } else if length > XMP_NAMESPACE_PREFIX.len()
        && decoder.stream.peek_at(0, XMP_NAMESPACE_PREFIX.len())? == XMP_NAMESPACE_PREFIX
    {
        trace!("XMP Data Present");
        decoder.stream.skip(XMP_NAMESPACE_PREFIX.len())?;
        length -= XMP_NAMESPACE_PREFIX.len();
        let xmp_data = decoder.stream.peek_at(0, length)?.to_vec();
        decoder.info.xmp_data = Some(xmp_data);
    } else {
        warn!("Unknown format for APP1 tag, skipping");
    }

    decoder.stream.skip(length)?;
    Ok(())
}

pub(crate) fn parse_app2<T: ZByteReaderTrait>(
    decoder: &mut JpegDecoder<T>
) -> Result<(), DecodeErrors> {
    static HDR_META: &[u8] = b"urn:iso:std:iso:ts:21496:-1\0";
    static MPF_DATA: &[u8] = b"MPF\0";

    let mut length = usize::from(decoder.stream.get_u16_be());

    if length < 2 {
        return Err(DecodeErrors::FormatStatic("Too small app2 segment"));
    }
    // length bytes
    length -= 2;

    if length > 14 && decoder.stream.peek_at(0, 12)? == *b"ICC_PROFILE\0" {
        trace!("ICC Profile present");
        // skip 12 bytes which indicate ICC profile
        length -= 12;
        decoder.stream.skip(12)?;
        let seq_no = decoder.stream.read_u8();
        let num_markers = decoder.stream.read_u8();
        // deduct the two bytes we read above
        length -= 2;

        let data = decoder.stream.peek_at(0, length)?.to_vec();

        let icc_chunk = ICCChunk {
            seq_no,
            num_markers,
            data
        };
        decoder.icc_data.push(icc_chunk);
    } else if length > HDR_META.len() && decoder.stream.peek_at(0, HDR_META.len())? == HDR_META {
        length = length.saturating_sub(HDR_META.len());
        decoder.stream.skip(HDR_META.len())?;
        trace!("Gain Map metadata found");
        match length {
            4 => {
                // If gain map metadata length == 4 then here it variables
                // https://github.com/google/libultrahdr/blob/bf2aa439eea9ad5da483003fa44182f990f74091/lib/src/jpegr.cpp#L1076C1-L1077C35
                // 2 bytes minimum_version: (00 00)
                // 2 bytes writer_version: (00 00)
                // Perhaps nothing to do with it ?
                let _ = decoder.stream.get_u16_be();
                let _ = decoder.stream.get_u16_be();
                length -= 4;
                decoder
                    .info
                    .gain_map_info
                    .push(GainMapInfo { data: Vec::new() });
            }
            n if n > 4 => {
                // If there is perhaps useful gain map info
                // we'll read this until end
                // https://github.com/google/libultrahdr/blob/bf2aa439eea9ad5da483003fa44182f990f74091/lib/src/jpegr.cpp#L1323
                let data = decoder.stream.peek_at(0, length)?.to_vec();
                length -= data.len();
                decoder.stream.skip(data.len())?;

                decoder.info.gain_map_info.push(GainMapInfo { data });
            }
            _ => {}
        }
    } else if length > MPF_DATA.len() && decoder.stream.peek_at(0, MPF_DATA.len())? == MPF_DATA {
        trace!("MPF Signature present");
        length = length.saturating_sub(MPF_DATA.len());
        decoder.stream.skip(MPF_DATA.len())?;
        // MPF signature taken from here
        // https://github.com/google/libultrahdr/blob/bf2aa439eea9ad5da483003fa44182f990f74091/lib/include/ultrahdr/multipictureformat.h#L50
        // https://github.com/google/libultrahdr/blob/bf2aa439eea9ad5da483003fa44182f990f74091/lib/src/multipictureformat.cpp#L36
        // More info https://www.cipa.jp/std/documents/e/DC-X007-KEY_E.pdf
        let data = decoder.stream.peek_at(0, length)?.to_vec();
        length -= data.len();
        decoder.stream.skip(data.len())?;
        decoder.info.multi_picture_information = Some(data);
    }

    decoder.stream.skip(length)?;

    Ok(())
}

/// Small utility function to print Un-zig-zagged quantization tables

fn un_zig_zag<T>(a: &[T]) -> [i32; 64]
where
    T: Default + Copy,
    i32: core::convert::From<T>
{
    let mut output = [i32::default(); 64];

    for i in 0..64 {
        output[UN_ZIGZAG[i]] = i32::from(a[i]);
    }

    output
}
