//! Project-owned boundaries for JPEG 2000 codec types exposed by the C# API.

use crate::{DEFAULT_MAX_PIXELS, Error, ManagedImage};
use std::any::Any;
use std::fmt::Debug;
use std::marker::PhantomData;

/// A type-erased decoded image returned by an image creator.
pub trait IImage: Any + Debug + Send + Sync {
    /// Returns the concrete image wrapper for checked downcasting.
    fn as_any(&self) -> &dyn Any;

    /// Returns the already-created managed image when this is a compatible
    /// decode target.
    fn as_managed_image(&self) -> Option<&ManagedImage> {
        None
    }
}

/// Marker corresponding to `CoreJ2K`'s image-creator boundary.
pub trait IImageCreator: Send + Sync {}

/// Typed marker corresponding to `CoreJ2K`'s generic image creator.
#[derive(Clone, Copy, Debug, Default)]
pub struct ImageCreator<T>(pub PhantomData<T>);

/// One decoded JPEG 2000 component at its original integer precision.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InterleavedComponent {
    precision: u8,
    signed: bool,
    alpha: bool,
    samples: Vec<i32>,
}

impl InterleavedComponent {
    /// Creates a checked component without reducing its sample precision.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Argument`] for precision outside 1 through 31 bits or
    /// for samples outside the declared signed or unsigned range.
    pub fn new(precision: u8, signed: bool, alpha: bool, samples: Vec<i32>) -> Result<Self, Error> {
        if !(1..=31).contains(&precision) {
            return Err(Error::Argument);
        }
        let (minimum, maximum) = component_range(precision, signed);
        if samples
            .iter()
            .any(|sample| i64::from(*sample) < minimum || i64::from(*sample) > maximum)
        {
            return Err(Error::Argument);
        }
        Ok(Self {
            precision,
            signed,
            alpha,
            samples,
        })
    }

    /// Declared component precision in bits.
    #[must_use]
    pub const fn precision(&self) -> u8 {
        self.precision
    }

    /// Whether component samples use signed representation.
    #[must_use]
    pub const fn is_signed(&self) -> bool {
        self.signed
    }

    /// Whether the container marks this component as alpha.
    #[must_use]
    pub const fn is_alpha(&self) -> bool {
        self.alpha
    }

    /// Original integer samples, in top-left row-major order.
    #[must_use]
    pub fn samples(&self) -> &[i32] {
        &self.samples
    }
}

/// A bounded decoded image retaining component precision and order.
///
/// The historical external type calls this representation interleaved, while
/// its public conversion API is component-oriented. Keeping the components in
/// separate owned planes avoids a second full-image allocation and retains the
/// original JPEG 2000 integer samples until byte conversion is requested.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InterleavedImage {
    width: i32,
    height: i32,
    components: Vec<InterleavedComponent>,
}

impl InterleavedImage {
    /// Creates a checked decoded component image.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Argument`] for invalid/over-limit dimensions, zero or
    /// more than five components, or a plane whose size differs from
    /// `width * height`.
    pub fn new(
        width: i32,
        height: i32,
        components: Vec<InterleavedComponent>,
    ) -> Result<Self, Error> {
        let pixels = checked_pixels(width, height)?;
        if components.is_empty()
            || components.len() > 5
            || components
                .iter()
                .any(|component| component.samples.len() != pixels)
        {
            return Err(Error::Argument);
        }
        pixels
            .checked_mul(components.len())
            .filter(|samples| *samples <= DEFAULT_MAX_PIXELS * 5)
            .ok_or(Error::Argument)?;
        Ok(Self {
            width,
            height,
            components,
        })
    }

    /// Decoded width after any discard-level reduction.
    #[must_use]
    pub const fn width(&self) -> i32 {
        self.width
    }

    /// Decoded height after any discard-level reduction.
    #[must_use]
    pub const fn height(&self) -> i32 {
        self.height
    }

    /// Number of decoded components in codestream order.
    #[must_use]
    pub fn number_of_components(&self) -> usize {
        self.components.len()
    }

    /// Component metadata and original samples.
    #[must_use]
    pub fn components(&self) -> &[InterleavedComponent] {
        &self.components
    }

    /// Scales one component into an 8-bit plane using CoreJ2K-compatible full
    /// range conversion.
    ///
    /// # Errors
    ///
    /// Returns [`Error::IndexOutOfRange`] for an invalid component index and
    /// [`Error::Argument`] when `destination` is not exactly one image plane.
    pub fn to_component_bytes(
        &self,
        component_index: usize,
        destination: &mut [u8],
    ) -> Result<(), Error> {
        let component = self
            .components
            .get(component_index)
            .ok_or(Error::IndexOutOfRange)?;
        if destination.len() != component.samples.len() {
            return Err(Error::Argument);
        }
        for (destination, sample) in destination.iter_mut().zip(&component.samples) {
            *destination = scale_sample(*sample, component.precision, component.signed);
        }
        Ok(())
    }
}

/// Encode-side sample source corresponding to `CoreJ2K`'s block image source.
pub trait BlkImgDataSrc: Debug + Send + Sync {
    /// Image width.
    fn width(&self) -> i32;
    /// Image height.
    fn height(&self) -> i32;
    /// Number of presented components.
    fn number_of_components(&self) -> usize;
    /// Nominal range bits for one component.
    ///
    /// # Errors
    ///
    /// Returns [`Error::IndexOutOfRange`] for an invalid component index.
    fn nominal_range_bits(&self, component_index: usize) -> Result<u8, Error>;
    /// Fixed-point fractional bits for one component.
    ///
    /// # Errors
    ///
    /// Returns [`Error::IndexOutOfRange`] for an invalid component index.
    fn fixed_point(&self, component_index: usize) -> Result<u8, Error>;
    /// Whether one presented component is signed at the source.
    ///
    /// # Errors
    ///
    /// Returns [`Error::IndexOutOfRange`] for an invalid component index.
    fn is_original_signed(&self, component_index: usize) -> Result<bool, Error>;
    /// Returns a checked top-left row-major rectangle with the source DC offset
    /// applied, exactly as `CoreJ2K` expects.
    ///
    /// # Errors
    ///
    /// Returns a typed index or argument error for an invalid component or
    /// rectangle.
    fn component_block(
        &self,
        component_index: usize,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
    ) -> Result<Vec<i32>, Error>;
}

fn checked_pixels(width: i32, height: i32) -> Result<usize, Error> {
    let width = usize::try_from(width).map_err(|_| Error::Argument)?;
    let height = usize::try_from(height).map_err(|_| Error::Argument)?;
    if width == 0 || height == 0 {
        return Err(Error::Argument);
    }
    width
        .checked_mul(height)
        .filter(|pixels| *pixels <= DEFAULT_MAX_PIXELS)
        .ok_or(Error::Argument)
}

fn component_range(precision: u8, signed: bool) -> (i64, i64) {
    if signed {
        let magnitude = 1_i64 << (precision - 1);
        (-magnitude, magnitude - 1)
    } else {
        (0, (1_i64 << precision) - 1)
    }
}

fn scale_sample(sample: i32, precision: u8, signed: bool) -> u8 {
    if signed {
        let old_max = 1_i64 << (precision - 1);
        let scaled = (i64::from(sample) * 128) / old_max + 128;
        u8::try_from(scaled.clamp(0, 255)).unwrap_or_default()
    } else {
        let old_max = (1_u64 << precision) - 1;
        let scaled = (u64::try_from(sample).unwrap_or_default() * 255) / old_max;
        u8::try_from(scaled).unwrap_or(u8::MAX)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn component_conversion_preserves_precision_and_signed_range() {
        let unsigned = InterleavedComponent::new(16, false, false, vec![0, 32_768, 65_535])
            .expect("16-bit component");
        let signed = InterleavedComponent::new(8, true, false, vec![-128, 0, 127])
            .expect("signed component");
        let unsigned = InterleavedImage::new(3, 1, vec![unsigned]).expect("unsigned image");
        let signed = InterleavedImage::new(3, 1, vec![signed]).expect("signed image");
        let mut bytes = [0; 3];
        unsigned
            .to_component_bytes(0, &mut bytes)
            .expect("unsigned bytes");
        assert_eq!(bytes, [0, 127, 255]);
        signed
            .to_component_bytes(0, &mut bytes)
            .expect("signed bytes");
        assert_eq!(bytes, [0, 128, 255]);
    }

    #[test]
    fn interleaved_image_rejects_bad_layouts_and_ranges() {
        assert_eq!(
            InterleavedComponent::new(0, false, false, vec![]),
            Err(Error::Argument)
        );
        assert_eq!(
            InterleavedComponent::new(8, false, false, vec![256]),
            Err(Error::Argument)
        );
        let component = InterleavedComponent::new(8, false, false, vec![0]).unwrap();
        assert_eq!(
            InterleavedImage::new(2, 1, vec![component]),
            Err(Error::Argument)
        );
    }
}
