//! LibreMetaverse-specific adapters around the format-neutral imaging boundary.

use libremetaverse_imaging::codec::{BlkImgDataSrc, IImage, IImageCreator, InterleavedImage};
use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels};
use libremetaverse_types::Error;
use libremetaverse_types::compat::Object;
use std::any::Any;
use std::sync::Arc;

const DC_OFFSET: i32 = 128;

/// CoreJ2K-compatible creator that writes directly into managed image planes.
#[derive(Clone, Copy, Debug, Default)]
pub struct ManagedImageCreator;

impl ManagedImageCreator {
    /// Creates a creator.
    ///
    /// # Errors
    ///
    /// This fixed compatibility signature cannot fail.
    pub const fn new() -> Result<Self, Error> {
        Ok(Self)
    }

    /// Builds a managed image from already byte-scaled interleaved samples.
    ///
    /// Component order follows the reference exactly: gray; gray/alpha; RGB;
    /// RGBA; or RGB/bump/alpha.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Argument`] for invalid dimensions, component counts,
    /// or byte lengths and [`Error::InvalidOperation`] on bounded allocation
    /// failure.
    #[allow(clippy::needless_pass_by_value)] // Fixed mapped `byte[]` ownership signature.
    pub fn create(
        &self,
        width: i32,
        height: i32,
        num_components: i32,
        bytes: Vec<u8>,
    ) -> Result<Box<dyn IImage>, Error> {
        let components = usize::try_from(num_components).map_err(|_| Error::Argument)?;
        let channels = channels_for_components(components)?;
        let image = managed_from_interleaved(width, height, channels, components, &bytes)?;
        Ok(Box::new(ManagedImageJ2kImage(image)))
    }

    /// Adapts a boxed managed image passed through the mapped `System.Object`
    /// boundary into the four-component encode source used by `CoreJ2K`.
    ///
    /// # Errors
    ///
    /// Returns [`Error::ArgumentNull`] for `Object::Undefined`,
    /// [`Error::Argument`] for another object type, or a validation/allocation
    /// failure for an inconsistent image.
    #[allow(clippy::needless_pass_by_value)] // Fixed mapped `System.Object` ownership signature.
    pub fn to_portable_image_source(
        &self,
        image_object: Object,
    ) -> Result<Box<dyn BlkImgDataSrc>, Error> {
        if image_object == Object::Undefined {
            return Err(Error::ArgumentNull);
        }
        let image = image_object
            .downcast_arc::<ManagedImage>()
            .ok_or(Error::Argument)?;
        image.validate()?;
        Ok(Box::new(ManagedImageBlockSource { image }))
    }

    /// Rust-native strongly typed form of [`Self::to_portable_image_source`].
    ///
    /// # Errors
    ///
    /// Returns [`Error::InvalidOperation`] for an inconsistent public image
    /// layout.
    pub fn to_portable_image_source_from_managed(
        &self,
        image: ManagedImage,
    ) -> Result<Box<dyn BlkImgDataSrc>, Error> {
        image.validate()?;
        Ok(Box::new(ManagedImageBlockSource {
            image: Arc::new(image),
        }))
    }
}

impl IImageCreator for ManagedImageCreator {}

#[derive(Debug)]
struct ManagedImageJ2kImage(ManagedImage);

impl IImage for ManagedImageJ2kImage {
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_managed_image(&self) -> Option<&ManagedImage> {
        Some(&self.0)
    }
}

/// Conversion helpers for the external `CoreJ2K` interleaved-image boundary.
#[derive(Clone, Copy, Debug, Default)]
pub struct ManagedImageInterleavedExtensions;

impl ManagedImageInterleavedExtensions {
    /// Converts original-precision component planes to managed 8-bit planes.
    ///
    /// # Errors
    ///
    /// Returns a typed component/layout/allocation error. Component order is
    /// gray; gray/alpha; RGB; RGBA; or RGB/bump/alpha.
    #[allow(clippy::needless_pass_by_value)] // Fixed mapped external-type ownership signature.
    pub fn to_managed_image(image: InterleavedImage) -> Result<ManagedImage, Error> {
        let components = image.number_of_components();
        let channels = channels_for_components(components)?;
        let width = image.width();
        let height = image.height();
        let pixels = checked_pixels(width, height)?;
        let length = pixels.checked_mul(components).ok_or(Error::Argument)?;
        let mut bytes = Vec::new();
        bytes
            .try_reserve_exact(length)
            .map_err(|_| Error::InvalidOperation)?;
        bytes.resize(length, 0);
        let mut plane = vec![0; pixels];
        for component in 0..components {
            image.to_component_bytes(component, &mut plane)?;
            for (pixel, sample) in plane.iter().enumerate() {
                bytes[pixel * components + component] = *sample;
            }
        }
        managed_from_interleaved(width, height, channels, components, &bytes)
    }
}

#[derive(Debug)]
struct ManagedImageBlockSource {
    image: Arc<ManagedImage>,
}

impl BlkImgDataSrc for ManagedImageBlockSource {
    fn width(&self) -> i32 {
        self.image.width
    }

    fn height(&self) -> i32 {
        self.image.height
    }

    fn number_of_components(&self) -> usize {
        4
    }

    fn nominal_range_bits(&self, component_index: usize) -> Result<u8, Error> {
        validate_component_index(component_index)?;
        Ok(8)
    }

    fn fixed_point(&self, component_index: usize) -> Result<u8, Error> {
        validate_component_index(component_index)?;
        Ok(0)
    }

    fn is_original_signed(&self, component_index: usize) -> Result<bool, Error> {
        validate_component_index(component_index)?;
        Ok(false)
    }

    fn component_block(
        &self,
        component_index: usize,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
    ) -> Result<Vec<i32>, Error> {
        validate_component_index(component_index)?;
        self.image.validate()?;
        let x = usize::try_from(x).map_err(|_| Error::Argument)?;
        let y = usize::try_from(y).map_err(|_| Error::Argument)?;
        let width = usize::try_from(width).map_err(|_| Error::Argument)?;
        let height = usize::try_from(height).map_err(|_| Error::Argument)?;
        let image_width = usize::try_from(self.image.width).map_err(|_| Error::Argument)?;
        let image_height = usize::try_from(self.image.height).map_err(|_| Error::Argument)?;
        let Some(x_end) = x.checked_add(width) else {
            return Err(Error::Argument);
        };
        let Some(y_end) = y.checked_add(height) else {
            return Err(Error::Argument);
        };
        if width == 0 || height == 0 || x_end > image_width || y_end > image_height {
            return Err(Error::Argument);
        }
        let sample_count = width.checked_mul(height).ok_or(Error::Argument)?;
        let mut samples = Vec::new();
        samples
            .try_reserve_exact(sample_count)
            .map_err(|_| Error::InvalidOperation)?;
        for row in y..y + height {
            for column in x..x + width {
                let pixel = row * image_width + column;
                samples.push(self.sample(component_index, pixel)? - DC_OFFSET);
            }
        }
        Ok(samples)
    }
}

impl ManagedImageBlockSource {
    fn sample(&self, component: usize, pixel: usize) -> Result<i32, Error> {
        let has_color = self
            .image
            .channels
            .contains(ManagedImageImageChannels::COLOR);
        let has_alpha = self
            .image
            .channels
            .contains(ManagedImageImageChannels::ALPHA);
        let value = if has_alpha {
            if has_color {
                match component {
                    0 => self.image.red[pixel],
                    1 => self.image.green[pixel],
                    2 => self.image.blue[pixel],
                    3 => self.image.alpha[pixel],
                    _ => return Err(Error::IndexOutOfRange),
                }
            } else if component == 3 {
                u8::MAX
            } else {
                self.image.alpha[pixel]
            }
        } else if has_color {
            match component {
                0 => self.image.red[pixel],
                1 => self.image.green[pixel],
                2 => self.image.blue[pixel],
                3 => u8::MAX,
                _ => return Err(Error::IndexOutOfRange),
            }
        } else {
            return Err(Error::InvalidOperation);
        };
        Ok(i32::from(value))
    }
}

fn managed_from_interleaved(
    width: i32,
    height: i32,
    channels: ManagedImageImageChannels,
    components: usize,
    bytes: &[u8],
) -> Result<ManagedImage, Error> {
    let pixels = checked_pixels(width, height)?;
    if bytes.len() != pixels.checked_mul(components).ok_or(Error::Argument)? {
        return Err(Error::Argument);
    }
    let mut image = ManagedImage::new(width, height, channels)?;
    for pixel in 0..pixels {
        let source = pixel * components;
        match components {
            1 => image.red[pixel] = bytes[source],
            2 => {
                image.red[pixel] = bytes[source];
                image.alpha[pixel] = bytes[source + 1];
            }
            3 => {
                image.red[pixel] = bytes[source];
                image.green[pixel] = bytes[source + 1];
                image.blue[pixel] = bytes[source + 2];
            }
            4 => {
                image.red[pixel] = bytes[source];
                image.green[pixel] = bytes[source + 1];
                image.blue[pixel] = bytes[source + 2];
                image.alpha[pixel] = bytes[source + 3];
            }
            5 => {
                image.red[pixel] = bytes[source];
                image.green[pixel] = bytes[source + 1];
                image.blue[pixel] = bytes[source + 2];
                image.bump[pixel] = bytes[source + 3];
                image.alpha[pixel] = bytes[source + 4];
            }
            _ => return Err(Error::Argument),
        }
    }
    Ok(image)
}

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 <= libremetaverse_imaging::DEFAULT_MAX_PIXELS)
        .ok_or(Error::Argument)
}

fn channels_for_components(components: usize) -> Result<ManagedImageImageChannels, Error> {
    Ok(match components {
        1 => ManagedImageImageChannels::GRAY,
        2 => ManagedImageImageChannels::GRAY | ManagedImageImageChannels::ALPHA,
        3 => ManagedImageImageChannels::COLOR,
        4 => ManagedImageImageChannels::COLOR | ManagedImageImageChannels::ALPHA,
        5 => {
            ManagedImageImageChannels::COLOR
                | ManagedImageImageChannels::BUMP
                | ManagedImageImageChannels::ALPHA
        }
        _ => return Err(Error::Argument),
    })
}

fn validate_component_index(component_index: usize) -> Result<(), Error> {
    if component_index < 4 {
        Ok(())
    } else {
        Err(Error::IndexOutOfRange)
    }
}

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

    #[test]
    fn creator_maps_all_reference_component_orders() {
        let creator = ManagedImageCreator::new().unwrap();
        let cases = [
            (1, vec![1], ManagedImageImageChannels::GRAY),
            (
                2,
                vec![1, 2],
                ManagedImageImageChannels::GRAY | ManagedImageImageChannels::ALPHA,
            ),
            (3, vec![1, 2, 3], ManagedImageImageChannels::COLOR),
            (
                4,
                vec![1, 2, 3, 4],
                ManagedImageImageChannels::COLOR | ManagedImageImageChannels::ALPHA,
            ),
            (
                5,
                vec![1, 2, 3, 4, 5],
                ManagedImageImageChannels::COLOR
                    | ManagedImageImageChannels::BUMP
                    | ManagedImageImageChannels::ALPHA,
            ),
        ];
        for (components, bytes, channels) in cases {
            let wrapper = creator.create(1, 1, components, bytes).unwrap();
            let image = wrapper.as_managed_image().expect("managed image");
            assert_eq!(image.channels, channels);
            assert_eq!(image.red, [1]);
            if components >= 3 {
                assert_eq!(image.green, [2]);
                assert_eq!(image.blue, [3]);
            }
            if components == 2 || components == 4 {
                assert_eq!(image.alpha[0], u8::try_from(components).unwrap());
            }
            if components == 5 {
                assert_eq!(image.bump, [4]);
                assert_eq!(image.alpha, [5]);
            }
        }
        assert!(matches!(
            creator.create(1, 1, 4, vec![0; 3]),
            Err(Error::Argument)
        ));
    }

    #[test]
    fn interleaved_extension_scales_samples_and_keeps_bump_before_alpha() {
        let components = vec![
            InterleavedComponent::new(16, false, false, vec![65_535]).unwrap(),
            InterleavedComponent::new(8, false, false, vec![2]).unwrap(),
            InterleavedComponent::new(8, false, false, vec![3]).unwrap(),
            InterleavedComponent::new(8, false, false, vec![4]).unwrap(),
            InterleavedComponent::new(8, false, true, vec![5]).unwrap(),
        ];
        let image = InterleavedImage::new(1, 1, components).unwrap();
        let image = ManagedImageInterleavedExtensions::to_managed_image(image).unwrap();
        assert_eq!(image.red, [255]);
        assert_eq!(image.green, [2]);
        assert_eq!(image.blue, [3]);
        assert_eq!(image.bump, [4]);
        assert_eq!(image.alpha, [5]);
    }

    #[test]
    fn portable_source_matches_dc_offset_and_channel_substitution() {
        let creator = ManagedImageCreator::new().unwrap();
        let mut image = ManagedImage::new(
            2,
            1,
            ManagedImageImageChannels::COLOR | ManagedImageImageChannels::ALPHA,
        )
        .unwrap();
        image.red.copy_from_slice(&[0, 255]);
        image.green.copy_from_slice(&[1, 2]);
        image.blue.copy_from_slice(&[3, 4]);
        image.alpha.copy_from_slice(&[5, 6]);
        let source = creator
            .to_portable_image_source(Object::opaque(image))
            .unwrap();
        assert_eq!(source.component_block(0, 0, 0, 2, 1).unwrap(), [-128, 127]);
        assert_eq!(source.component_block(3, 0, 0, 2, 1).unwrap(), [-123, -122]);
        assert_eq!(source.nominal_range_bits(0), Ok(8));
        assert_eq!(source.fixed_point(0), Ok(0));
        assert_eq!(source.is_original_signed(0), Ok(false));
        assert_eq!(
            source.component_block(4, 0, 0, 1, 1),
            Err(Error::IndexOutOfRange)
        );

        let mut alpha = ManagedImage::new(1, 1, ManagedImageImageChannels::ALPHA).unwrap();
        alpha.alpha[0] = 42;
        let source = creator
            .to_portable_image_source(Object::opaque(alpha))
            .unwrap();
        assert_eq!(source.component_block(0, 0, 0, 1, 1).unwrap(), [-86]);
        assert_eq!(source.component_block(3, 0, 0, 1, 1).unwrap(), [127]);
    }
}
