//! Native implementation of the pinned `SkiaTextureCodec` behavior.

use crate::Error;
use crate::backend::{SKAlphaType, SKBitmap, SKColorType};
#[cfg(feature = "skia")]
use libremetaverse_imaging::{DEFAULT_MAX_ENCODED_BYTES, DEFAULT_MAX_PIXELS};
use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels};
use libremetaverse_types::compat::ReadWrite;
#[cfg(feature = "skia")]
use std::io::Read;

/// Optional Skia-backed decoder and format-neutral bitmap converter.
#[derive(Clone, Copy, Debug, Default)]
pub struct SkiaTextureCodec;

impl SkiaTextureCodec {
    /// Creates the stateless codec adapter.
    ///
    /// # Errors
    ///
    /// This fixed compatibility constructor cannot fail.
    pub const fn new() -> Result<Self, Error> {
        Ok(Self)
    }

    /// Decodes a bounded compressed-image stream through Skia.
    ///
    /// # Errors
    ///
    /// With the `skia` feature, returns a typed argument, parse, or operation
    /// error for oversized or malformed input and unsupported native output.
    /// Without the feature it returns [`Error::InvalidOperation`].
    pub fn decode(&self, mut stream: Box<dyn ReadWrite + Send>) -> Result<ManagedImage, Error> {
        #[cfg(feature = "skia")]
        {
            let mut encoded = Vec::new();
            Read::by_ref(&mut stream)
                .take((DEFAULT_MAX_ENCODED_BYTES + 1) as u64)
                .read_to_end(&mut encoded)
                .map_err(|_| Error::InvalidOperation)?;
            if encoded.is_empty() {
                return Err(Error::InvalidOperation);
            }
            if encoded.len() > DEFAULT_MAX_ENCODED_BYTES {
                return Err(Error::Argument);
            }
            decode_with_skia(&encoded)
        }
        #[cfg(not(feature = "skia"))]
        {
            let _ = &mut stream;
            Err(Error::InvalidOperation)
        }
    }

    /// Converts an owned mapped bitmap using the exact reference channel rules.
    ///
    /// # Errors
    ///
    /// Returns a typed error for invalid dimensions, storage, or an unsupported
    /// fallback byte width.
    #[allow(clippy::needless_pass_by_value)] // fixed mapped C# signature
    pub fn to_managed_image(bitmap: SKBitmap) -> Result<ManagedImage, Error> {
        bitmap_to_managed(&bitmap)
    }
}

// Keeping the format branches together makes comparison with the pinned C#
// color switch auditable and prevents subtly different indexing paths.
#[allow(clippy::too_many_lines)]
fn bitmap_to_managed(bitmap: &SKBitmap) -> Result<ManagedImage, Error> {
    let width = usize::try_from(bitmap.width()).map_err(|_| Error::Argument)?;
    let height = usize::try_from(bitmap.height()).map_err(|_| Error::Argument)?;
    let reference_step = bitmap.row_bytes() / width.max(1);
    let channels = match bitmap.color_type() {
        SKColorType::Rgb565 => ManagedImageImageChannels::COLOR,
        SKColorType::Bgra8888
        | SKColorType::Rgba8888
        | SKColorType::Rgba1010102
        | SKColorType::Bgra1010102 => {
            ManagedImageImageChannels::COLOR | ManagedImageImageChannels::ALPHA
        }
        SKColorType::Gray8 => ManagedImageImageChannels::GRAY,
        SKColorType::Alpha8 => ManagedImageImageChannels::ALPHA,
        SKColorType::Other { .. } if reference_step == 1 => ManagedImageImageChannels::GRAY,
        SKColorType::Other { .. } if reference_step == 3 => ManagedImageImageChannels::COLOR,
        SKColorType::Other { .. } if reference_step == 4 => {
            ManagedImageImageChannels::COLOR | ManagedImageImageChannels::ALPHA
        }
        SKColorType::Other { .. } => return Err(Error::InvalidOperation),
    };
    let mut image = ManagedImage::new(bitmap.width(), bitmap.height(), channels)?;
    for y in 0..height {
        let row = y.checked_mul(bitmap.row_bytes()).ok_or(Error::Argument)?;
        for x in 0..width {
            let pixel = y
                .checked_mul(width)
                .and_then(|offset| offset.checked_add(x))
                .ok_or(Error::Argument)?;
            match bitmap.color_type() {
                SKColorType::Rgb565 => {
                    let offset = checked_offset(row, x, 2, bitmap.pixels().len())?;
                    let packed =
                        u16::from_le_bytes([bitmap.pixels()[offset], bitmap.pixels()[offset + 1]]);
                    image.red[pixel] = scale_bits(u32::from((packed >> 11) & 0x1f), 31);
                    image.green[pixel] = scale_bits(u32::from((packed >> 5) & 0x3f), 63);
                    image.blue[pixel] = scale_bits(u32::from(packed & 0x1f), 31);
                }
                SKColorType::Bgra8888 | SKColorType::Rgba8888 => {
                    let offset = checked_offset(row, x, reference_step, bitmap.pixels().len())?;
                    let source = &bitmap.pixels()[offset..offset + 4];
                    let (red, green, blue, alpha) = if bitmap.color_type() == SKColorType::Rgba8888
                    {
                        (source[0], source[1], source[2], source[3])
                    } else {
                        (source[2], source[1], source[0], source[3])
                    };
                    let (red, green, blue) =
                        normalize_8bit_alpha(red, green, blue, alpha, bitmap.alpha_type());
                    image.red[pixel] = red;
                    image.green[pixel] = green;
                    image.blue[pixel] = blue;
                    image.alpha[pixel] = alpha;
                }
                SKColorType::Rgba1010102 | SKColorType::Bgra1010102 => {
                    let offset = checked_offset(row, x, 4, bitmap.pixels().len())?;
                    let packed = u32::from_le_bytes(
                        bitmap.pixels()[offset..offset + 4]
                            .try_into()
                            .map_err(|_| Error::Argument)?,
                    );
                    let first = packed & 0x3ff;
                    let green = (packed >> 10) & 0x3ff;
                    let third = (packed >> 20) & 0x3ff;
                    let alpha = (packed >> 30) & 0x3;
                    let (red, blue) = if bitmap.color_type() == SKColorType::Rgba1010102 {
                        (first, third)
                    } else {
                        (third, first)
                    };
                    let (red, green, blue) =
                        normalize_10bit_alpha(red, green, blue, alpha, bitmap.alpha_type());
                    image.red[pixel] = scale_bits(red, 1023);
                    image.green[pixel] = scale_bits(green, 1023);
                    image.blue[pixel] = scale_bits(blue, 1023);
                    image.alpha[pixel] = u8::try_from(alpha * 85).map_err(|_| Error::Argument)?;
                }
                SKColorType::Gray8 => {
                    let offset = checked_offset(row, x, reference_step, bitmap.pixels().len())?;
                    image.red[pixel] = bitmap.pixels()[offset];
                }
                SKColorType::Alpha8 => {
                    let offset = checked_offset(row, x, reference_step, bitmap.pixels().len())?;
                    image.alpha[pixel] = bitmap.pixels()[offset];
                }
                SKColorType::Other { .. } => {
                    let offset = checked_offset(row, x, reference_step, bitmap.pixels().len())?;
                    match reference_step {
                        4 => {
                            let alpha = bitmap.pixels()[offset + 3];
                            let (red, green, blue) = normalize_8bit_alpha(
                                bitmap.pixels()[offset + 2],
                                bitmap.pixels()[offset + 1],
                                bitmap.pixels()[offset],
                                alpha,
                                bitmap.alpha_type(),
                            );
                            image.red[pixel] = red;
                            image.green[pixel] = green;
                            image.blue[pixel] = blue;
                            image.alpha[pixel] = alpha;
                        }
                        3 => {
                            image.blue[pixel] = bitmap.pixels()[offset];
                            image.green[pixel] = bitmap.pixels()[offset + 1];
                            image.red[pixel] = bitmap.pixels()[offset + 2];
                        }
                        1 => image.red[pixel] = bitmap.pixels()[offset],
                        _ => return Err(Error::InvalidOperation),
                    }
                }
            }
        }
    }
    Ok(image)
}

fn checked_offset(row: usize, x: usize, step: usize, length: usize) -> Result<usize, Error> {
    let offset = x
        .checked_mul(step)
        .and_then(|offset| row.checked_add(offset))
        .ok_or(Error::Argument)?;
    offset
        .checked_add(step)
        .filter(|end| *end <= length)
        .map(|_| offset)
        .ok_or(Error::Argument)
}

fn scale_bits(value: u32, maximum: u32) -> u8 {
    u8::try_from((value * 255 + maximum / 2) / maximum).unwrap_or(u8::MAX)
}

fn normalize_8bit_alpha(
    red: u8,
    green: u8,
    blue: u8,
    alpha: u8,
    alpha_type: SKAlphaType,
) -> (u8, u8, u8) {
    if alpha_type != SKAlphaType::Premul || alpha == u8::MAX {
        return (red, green, blue);
    }
    if alpha == 0 {
        return (0, 0, 0);
    }
    let normalize = |value: u8| {
        u8::try_from((u32::from(value) * 255 + u32::from(alpha) / 2) / u32::from(alpha))
            .unwrap_or(u8::MAX)
    };
    (normalize(red), normalize(green), normalize(blue))
}

fn normalize_10bit_alpha(
    red: u32,
    green: u32,
    blue: u32,
    alpha: u32,
    alpha_type: SKAlphaType,
) -> (u32, u32, u32) {
    if alpha_type != SKAlphaType::Premul || alpha == 3 {
        return (red, green, blue);
    }
    if alpha == 0 {
        return (0, 0, 0);
    }
    let normalize = |value: u32| (value * 3 + alpha / 2) / alpha;
    (normalize(red), normalize(green), normalize(blue))
}

#[cfg(feature = "skia")]
fn decode_with_skia(encoded: &[u8]) -> Result<ManagedImage, Error> {
    use skia_safe::Data;
    use skia_safe::codec::{Codec, Options, Result as CodecResult};

    let mut codec = Codec::from_data(Data::new_copy(encoded)).ok_or(Error::InvalidOperation)?;
    let source_info = codec.info();
    let width = usize::try_from(source_info.width()).map_err(|_| Error::Argument)?;
    let height = usize::try_from(source_info.height()).map_err(|_| Error::Argument)?;
    width
        .checked_mul(height)
        .filter(|pixels| *pixels > 0 && *pixels <= DEFAULT_MAX_PIXELS)
        .ok_or(Error::Argument)?;
    // Match SKBitmap.Decode: decode into Skia's native alpha representation,
    // then normalize premultiplied channels at the abstraction boundary.
    let target_info = source_info.clone();
    let row_bytes = target_info.min_row_bytes();
    let length = target_info.compute_byte_size(row_bytes);
    if length == usize::MAX || length > DEFAULT_MAX_PIXELS * 16 {
        return Err(Error::Argument);
    }
    let mut pixels = Vec::new();
    pixels
        .try_reserve_exact(length)
        .map_err(|_| Error::InvalidOperation)?;
    pixels.resize(length, 0);
    let result = codec.get_pixels_with_options(
        &target_info,
        &mut pixels,
        row_bytes,
        Some(&Options {
            max_decode_memory: Some(DEFAULT_MAX_PIXELS * 16),
            ..Options::default()
        }),
    );
    if result != CodecResult::Success {
        return Err(Error::InvalidOperation);
    }
    let bitmap = SKBitmap::new(
        source_info.width(),
        source_info.height(),
        row_bytes,
        map_color_type(target_info.color_type(), target_info.bytes_per_pixel()),
        map_alpha_type(target_info.alpha_type()),
        pixels,
    )?;
    bitmap_to_managed(&bitmap)
}

#[cfg(feature = "skia")]
const fn map_color_type(color: skia_safe::ColorType, bytes_per_pixel: usize) -> SKColorType {
    use skia_safe::ColorType;
    match color {
        ColorType::RGB565 => SKColorType::Rgb565,
        ColorType::BGRA8888 => SKColorType::Bgra8888,
        ColorType::RGBA8888 => SKColorType::Rgba8888,
        ColorType::RGBA1010102 => SKColorType::Rgba1010102,
        ColorType::BGRA1010102 => SKColorType::Bgra1010102,
        ColorType::Gray8 => SKColorType::Gray8,
        ColorType::Alpha8 => SKColorType::Alpha8,
        _ => SKColorType::Other { bytes_per_pixel },
    }
}

#[cfg(feature = "skia")]
const fn map_alpha_type(alpha: skia_safe::AlphaType) -> SKAlphaType {
    use skia_safe::AlphaType;
    match alpha {
        AlphaType::Opaque => SKAlphaType::Opaque,
        AlphaType::Premul => SKAlphaType::Premul,
        AlphaType::Unpremul => SKAlphaType::Unpremul,
        AlphaType::Unknown => SKAlphaType::Unknown,
    }
}

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

    #[test]
    fn mapped_bitmap_formats_preserve_reference_channel_rules() {
        let rgba = SKBitmap::new(
            2,
            1,
            8,
            SKColorType::Rgba8888,
            SKAlphaType::Unpremul,
            vec![1, 2, 3, 4, 5, 6, 7, 8],
        )
        .unwrap();
        let image = SkiaTextureCodec::to_managed_image(rgba).unwrap();
        assert_eq!(image.red, [1, 5]);
        assert_eq!(image.green, [2, 6]);
        assert_eq!(image.blue, [3, 7]);
        assert_eq!(image.alpha, [4, 8]);

        let rgb565 = SKBitmap::new(
            3,
            1,
            6,
            SKColorType::Rgb565,
            SKAlphaType::Opaque,
            vec![0x00, 0xf8, 0xe0, 0x07, 0x1f, 0x00],
        )
        .unwrap();
        let image = SkiaTextureCodec::to_managed_image(rgb565).unwrap();
        assert_eq!(image.red, [255, 0, 0]);
        assert_eq!(image.green, [0, 255, 0]);
        assert_eq!(image.blue, [0, 0, 255]);

        // The reference fallback derives its byte width from rowBytes / width,
        // even when the declared native format has a different packed width.
        let padded_unknown = SKBitmap::new(
            2,
            1,
            8,
            SKColorType::Other { bytes_per_pixel: 3 },
            SKAlphaType::Unpremul,
            vec![3, 2, 1, 4, 7, 6, 5, 8],
        )
        .unwrap();
        let image = SkiaTextureCodec::to_managed_image(padded_unknown).unwrap();
        assert_eq!(
            image.channels,
            ManagedImageImageChannels::COLOR | ManagedImageImageChannels::ALPHA
        );
        assert_eq!(image.red, [1, 5]);
        assert_eq!(image.green, [2, 6]);
        assert_eq!(image.blue, [3, 7]);
        assert_eq!(image.alpha, [4, 8]);
    }

    #[test]
    fn grayscale_alpha_ten_bit_and_premul_are_converted() {
        let gray = SKBitmap::new(
            2,
            1,
            2,
            SKColorType::Gray8,
            SKAlphaType::Opaque,
            vec![11, 239],
        )
        .unwrap();
        let gray = SkiaTextureCodec::to_managed_image(gray).unwrap();
        assert_eq!(gray.channels, ManagedImageImageChannels::GRAY);
        assert_eq!(gray.red, [11, 239]);

        let premul = SKBitmap::new(
            1,
            1,
            4,
            SKColorType::Bgra8888,
            SKAlphaType::Premul,
            vec![25, 50, 100, 128],
        )
        .unwrap();
        let premul = SkiaTextureCodec::to_managed_image(premul).unwrap();
        assert_eq!(premul.red, [199]);
        assert_eq!(premul.green, [100]);
        assert_eq!(premul.blue, [50]);
        assert_eq!(premul.alpha, [128]);

        let transparent_premul = SKBitmap::new(
            1,
            1,
            4,
            SKColorType::Rgba8888,
            SKAlphaType::Premul,
            vec![100, 50, 25, 0],
        )
        .unwrap();
        let transparent_premul = SkiaTextureCodec::to_managed_image(transparent_premul).unwrap();
        assert_eq!(transparent_premul.red, [0]);
        assert_eq!(transparent_premul.green, [0]);
        assert_eq!(transparent_premul.blue, [0]);
        assert_eq!(transparent_premul.alpha, [0]);

        let packed = 1023_u32 | (512 << 10) | (1 << 20) | (3 << 30);
        let ten_bit = SKBitmap::new(
            1,
            1,
            4,
            SKColorType::Rgba1010102,
            SKAlphaType::Unpremul,
            packed.to_le_bytes().to_vec(),
        )
        .unwrap();
        let ten_bit = SkiaTextureCodec::to_managed_image(ten_bit).unwrap();
        assert_eq!(ten_bit.red, [255]);
        assert_eq!(ten_bit.green, [128]);
        assert_eq!(ten_bit.blue, [0]);
        assert_eq!(ten_bit.alpha, [255]);

        let packed = 1023_u32 | (512 << 10) | (1 << 20) | (2 << 30);
        let ten_bit = SKBitmap::new(
            1,
            1,
            4,
            SKColorType::Bgra1010102,
            SKAlphaType::Unpremul,
            packed.to_le_bytes().to_vec(),
        )
        .unwrap();
        let ten_bit = SkiaTextureCodec::to_managed_image(ten_bit).unwrap();
        assert_eq!(ten_bit.red, [0]);
        assert_eq!(ten_bit.green, [128]);
        assert_eq!(ten_bit.blue, [255]);
        assert_eq!(ten_bit.alpha, [170]);
    }

    #[test]
    fn alpha_and_reference_fallback_layouts_are_converted_or_rejected() {
        let alpha = SKBitmap::new(
            2,
            1,
            2,
            SKColorType::Alpha8,
            SKAlphaType::Unpremul,
            vec![17, 241],
        )
        .unwrap();
        let alpha = SkiaTextureCodec::to_managed_image(alpha).unwrap();
        assert_eq!(alpha.channels, ManagedImageImageChannels::ALPHA);
        assert_eq!(alpha.alpha, [17, 241]);

        let bgr = SKBitmap::new(
            2,
            1,
            6,
            SKColorType::Other { bytes_per_pixel: 3 },
            SKAlphaType::Opaque,
            vec![3, 2, 1, 6, 5, 4],
        )
        .unwrap();
        let bgr = SkiaTextureCodec::to_managed_image(bgr).unwrap();
        assert_eq!(bgr.channels, ManagedImageImageChannels::COLOR);
        assert_eq!(bgr.red, [1, 4]);
        assert_eq!(bgr.green, [2, 5]);
        assert_eq!(bgr.blue, [3, 6]);

        let gray = SKBitmap::new(
            2,
            1,
            2,
            SKColorType::Other { bytes_per_pixel: 1 },
            SKAlphaType::Opaque,
            vec![23, 229],
        )
        .unwrap();
        let gray = SkiaTextureCodec::to_managed_image(gray).unwrap();
        assert_eq!(gray.channels, ManagedImageImageChannels::GRAY);
        assert_eq!(gray.red, [23, 229]);

        let unsupported = SKBitmap::new(
            1,
            1,
            2,
            SKColorType::Other { bytes_per_pixel: 2 },
            SKAlphaType::Opaque,
            vec![0, 0],
        )
        .unwrap();
        assert_eq!(
            SkiaTextureCodec::to_managed_image(unsupported),
            Err(Error::InvalidOperation)
        );
    }

    #[test]
    fn bitmap_bounds_and_feature_disabled_decode_fail_typed() {
        assert_eq!(
            SKBitmap::new(0, 1, 0, SKColorType::Gray8, SKAlphaType::Opaque, Vec::new(),),
            Err(Error::Argument)
        );
        assert_eq!(
            SKBitmap::new(
                2,
                2,
                4,
                SKColorType::Rgba8888,
                SKAlphaType::Unpremul,
                vec![0; 16],
            ),
            Err(Error::Argument)
        );
        #[cfg(not(feature = "skia"))]
        assert_eq!(
            SkiaTextureCodec.decode(Box::new(std::io::Cursor::new(Vec::new()))),
            Err(Error::InvalidOperation)
        );
    }

    #[cfg(feature = "skia")]
    #[test]
    fn feature_decodes_known_png_with_straight_alpha() {
        const PNG: &[u8] = &[
            0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48,
            0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00,
            0x00, 0x1f, 0x15, 0xc4, 0x89, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x44, 0x41, 0x54, 0x78,
            0x9c, 0x63, 0xf8, 0xcf, 0xc0, 0xf0, 0x1f, 0x00, 0x05, 0x00, 0x01, 0xff, 0x89, 0x99,
            0x3d, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
        ];
        let image = SkiaTextureCodec
            .decode(Box::new(std::io::Cursor::new(PNG.to_vec())))
            .expect("decode PNG");
        assert_eq!((image.width, image.height), (1, 1));
        assert_eq!(image.red, [255]);
        assert_eq!(image.green, [0]);
        assert_eq!(image.blue, [0]);
        assert_eq!(image.alpha, [255]);

        assert_eq!(
            SkiaTextureCodec.decode(Box::new(std::io::Cursor::new(Vec::new()))),
            Err(Error::InvalidOperation)
        );
        assert_eq!(
            SkiaTextureCodec.decode(Box::new(std::io::Cursor::new(b"not an image".to_vec()))),
            Err(Error::InvalidOperation)
        );
    }

    #[cfg(feature = "skia")]
    #[test]
    fn feature_decodes_webp_from_the_cross_platform_cache_configuration() {
        const WEBP: &[u8] = &[
            0x52, 0x49, 0x46, 0x46, 0x1c, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50,
            0x38, 0x4c, 0x0f, 0x00, 0x00, 0x00, 0x2f, 0x01, 0x40, 0x00, 0x00, 0x07, 0x10, 0xf5,
            0x8f, 0xfe, 0x07, 0x22, 0xa2, 0xff, 0x01, 0x00,
        ];
        let image = SkiaTextureCodec
            .decode(Box::new(std::io::Cursor::new(WEBP.to_vec())))
            .expect("decode WebP");
        assert_eq!((image.width, image.height), (2, 2));
        assert_eq!(image.red, [254; 4]);
        assert_eq!(image.green, [0; 4]);
        assert_eq!(image.blue, [0; 4]);
        assert_eq!(image.alpha, [255; 4]);
    }
}
