use crate::utils::containers::Array2D;
use anyhow::{Result, anyhow};
use half::f16;
use rayon::prelude::{IndexedParallelIterator, ParallelIterator, ParallelSliceMut};
use shared::Float;
use shared::core::color::{ColorEncoding, ColorEncodingTrait, LINEAR};
use shared::core::geometry::{Bounds2f, Point2f, Point2i};
use shared::core::image::{DeviceImage, ImageBase, PixelFormat, Pixels, WrapMode, WrapMode2D};
use shared::utils::math::square;
use smallvec::{SmallVec, smallvec};
use std::ops::{Deref, DerefMut};
use std::sync::Arc;

pub mod io;
pub mod metadata;
pub mod ops;
pub mod pixel;

pub use io::ImageIO;
pub use metadata::*;

#[derive(Clone, Debug, Default)]
pub struct ImageChannelValues(pub SmallVec<[Float; 4]>);

impl ImageChannelValues {
    pub fn average(&self) -> Float {
        if self.0.is_empty() {
            return 0.0;
        }
        let sum: Float = self.0.iter().sum();
        sum / (self.0.len() as Float)
    }

    pub fn max_value(&self) -> Float {
        self.0.iter().fold(Float::MIN, |a, &b| a.max(b))
    }
}

impl From<&[Float]> for ImageChannelValues {
    fn from(slice: &[Float]) -> Self {
        Self(SmallVec::from_slice(slice))
    }
}

impl From<Vec<Float>> for ImageChannelValues {
    fn from(vec: Vec<Float>) -> Self {
        Self(SmallVec::from_vec(vec))
    }
}

impl<const N: usize> From<[Float; N]> for ImageChannelValues {
    fn from(arr: [Float; N]) -> Self {
        Self(SmallVec::from_slice(&arr))
    }
}

impl Deref for ImageChannelValues {
    type Target = SmallVec<[Float; 4]>;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for ImageChannelValues {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

#[derive(Debug, Clone)]
pub enum PixelStorage {
    U8(Vec<u8>),
    F16(Vec<f16>),
    F32(Vec<f32>),
}

impl PixelStorage {
    pub fn as_pixels(&self) -> Pixels {
        match self {
            PixelStorage::U8(data) => Pixels::U8(data.as_ptr().into()),
            PixelStorage::F16(data) => Pixels::F16((data.as_ptr() as *const f16).into()),
            PixelStorage::F32(data) => Pixels::F32(data.as_ptr().into()),
        }
    }

    pub fn format(&self) -> PixelFormat {
        match self {
            PixelStorage::U8(_) => PixelFormat::U8,
            PixelStorage::F16(_) => PixelFormat::F16,
            PixelStorage::F32(_) => PixelFormat::F32,
        }
    }

    pub fn len(&self) -> usize {
        match self {
            PixelStorage::U8(d) => d.len(),
            PixelStorage::F16(d) => d.len(),
            PixelStorage::F32(d) => d.len(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct Image {
    pub pixels: PixelStorage,
    pub channel_names: Vec<String>,
    pub device: DeviceImage,
}

// impl Deref for Image {
//     type Target = DeviceImage;
//     #[inline]
//     fn deref(&self) -> &Self::Target {
//         &self.device
//     }
// }

#[derive(Debug, Clone)]
pub struct ImageAndMetadata {
    pub image: Image,
    pub metadata: ImageMetadata,
}

impl Image {
    // Constructors
    fn from_storage(
        storage: PixelStorage,
        resolution: Point2i,
        channel_names: &[impl AsRef<str>],
        encoding: ColorEncoding,
    ) -> Self {
        let n_channels = channel_names.len() as i32;
        let expected = (resolution.x() * resolution.y()) as usize * n_channels as usize;
        let channel_names = channel_names
            .iter()
            .map(|s| s.as_ref().to_string())
            .collect();
        assert_eq!(storage.len(), expected, "Pixel data size mismatch");

        let device = DeviceImage {
            base: ImageBase {
                format: storage.format(),
                encoding,
                resolution,
                n_channels,
            },
            pixels: storage.as_pixels(),
        };

        Self {
            pixels: storage,
            channel_names,
            device,
        }
    }

    pub fn from_u8(
        data: Vec<u8>,
        resolution: Point2i,
        channel_names: &[impl AsRef<str>],
        encoding: ColorEncoding,
    ) -> Self {
        Self::from_storage(PixelStorage::U8(data), resolution, channel_names, encoding)
    }

    pub fn from_f16(
        data: Vec<half::f16>,
        resolution: Point2i,
        channel_names: &[impl AsRef<str>],
    ) -> Self {
        Self::from_storage(PixelStorage::F16(data), resolution, channel_names, LINEAR)
    }

    pub fn from_f32(
        data: Vec<f32>,
        resolution: Point2i,
        channel_names: &[impl AsRef<str>],
    ) -> Self {
        Self::from_storage(PixelStorage::F32(data), resolution, channel_names, LINEAR)
    }

    pub fn new(
        format: PixelFormat,
        resolution: Point2i,
        channel_names: &[impl AsRef<str>],
        encoding: Arc<ColorEncoding>,
    ) -> Self {
        let n_channels = channel_names.len();
        let pixel_count = (resolution.x() * resolution.y()) as usize * n_channels;

        let storage = match format {
            PixelFormat::U8 => PixelStorage::U8(vec![0; pixel_count].into()),
            PixelFormat::F16 => PixelStorage::F16(vec![f16::ZERO; pixel_count].into()),
            PixelFormat::F32 => PixelStorage::F32(vec![0.0; pixel_count].into()),
        };

        Self::from_storage(storage, resolution, channel_names, *encoding)
    }

    pub fn new_constant(
        resolution: Point2i,
        channel_names: &[impl AsRef<str>],
        values: &[f32],
    ) -> Self {
        let n_channels = channel_names.len();
        if values.len() != n_channels {
            panic!(
                "Image::new_constant: values length ({}) must match channel count ({})",
                values.len(),
                n_channels
            );
        }

        let n_pixels = (resolution.x() * resolution.y()) as usize;

        let mut data = Vec::with_capacity(n_pixels * n_channels);

        for _ in 0..n_pixels {
            data.extend_from_slice(values);
        }

        Self::from_f32(data, resolution, channel_names)
    }

    // Access
    pub fn device(&self) -> &DeviceImage {
        &self.device
    }

    pub fn resolution(&self) -> Point2i {
        self.base.resolution
    }

    fn n_channels(&self) -> i32 {
        self.base().n_channels
    }

    pub fn format(&self) -> PixelFormat {
        self.base().format
    }

    pub fn channel_names(&self) -> Vec<&str> {
        self.channel_names.iter().map(|s| s.as_str()).collect()
    }

    pub fn encoding(&self) -> ColorEncoding {
        self.base().encoding
    }

    fn pixel_offset(&self, p: Point2i) -> usize {
        let width = self.resolution().x() as usize;
        let idx = p.y() as usize * width + p.x() as usize;
        idx * self.n_channels() as usize
    }

    // Read
    pub fn as_f32_slice(&self) -> Option<&[f32]> {
        match &self.pixels {
            PixelStorage::F32(data) => Some(data.as_slice()),
            _ => None,
        }
    }
    pub fn get_channel(&self, p: Point2i, c: i32) -> Float {
        self.get_channel_with_wrap(p, c, WrapMode::Clamp.into())
    }

    pub fn get_channel_with_wrap(&self, mut p: Point2i, c: i32, wrap_mode: WrapMode2D) -> Float {
        if !self.device.base.remap_pixel_coords(&mut p, wrap_mode) {
            return 0.0;
        }

        let offset = self.pixel_offset(p) + c as usize;

        match &self.pixels {
            PixelStorage::U8(data) => self.device.base.encoding.to_linear_scalar(data[offset]),
            PixelStorage::F16(data) => data[offset].to_f32(),
            PixelStorage::F32(data) => data[offset],
        }
    }

    pub fn get_channels(&self, p: Point2i) -> ImageChannelValues {
        self.get_channels_with_wrap(p, WrapMode::Clamp.into())
    }

    pub fn get_channels_with_wrap(
        &self,
        mut p: Point2i,
        wrap_mode: WrapMode2D,
    ) -> ImageChannelValues {
        if !self.device.base.remap_pixel_coords(&mut p, wrap_mode) {
            return ImageChannelValues(smallvec![0.0; self.n_channels() as usize]);
        }

        let offset = self.pixel_offset(p);
        let nc = self.n_channels() as usize;
        let mut values = SmallVec::with_capacity(nc);

        match &self.pixels {
            PixelStorage::U8(data) => {
                for i in 0..nc {
                    values.push(self.device.base.encoding.to_linear_scalar(data[offset + i]));
                }
            }
            PixelStorage::F16(data) => {
                for i in 0..nc {
                    values.push(data[offset + i].to_f32());
                }
            }
            PixelStorage::F32(data) => {
                for i in 0..nc {
                    values.push(data[offset + i]);
                }
            }
        }

        ImageChannelValues(values)
    }

    // Write
    pub fn set_channel(&mut self, p: Point2i, c: i32, mut value: Float) {
        if value.is_nan() {
            value = 0.0;
        }

        let res = self.resolution();
        if p.x() < 0 || p.x() >= res.x() || p.y() < 0 || p.y() >= res.y() {
            return;
        }

        let offset = self.pixel_offset(p) + c as usize;

        match &mut self.pixels {
            PixelStorage::U8(data) => {
                data[offset] = self.device.base.encoding.from_linear_scalar(value);
            }
            PixelStorage::F16(data) => {
                data[offset] = f16::from_f32(value);
            }
            PixelStorage::F32(data) => {
                data[offset] = value;
            }
        }
    }

    pub fn set_channels(&mut self, p: Point2i, values: &ImageChannelValues) {
        for i in 0..values.len() {
            self.set_channel(p, i.try_into().unwrap(), values[i])
        }
    }

    // Descriptions
    pub fn get_channels_with_desc(
        &self,
        p: Point2i,
        desc: &ImageChannelDesc,
        wrap_mode: WrapMode2D,
    ) -> ImageChannelValues {
        let mut pp = p;
        if !self.device.base.remap_pixel_coords(&mut pp, wrap_mode) {
            return ImageChannelValues(smallvec![0.0; desc.offset.len()]);
        }

        let pixel_offset = self.pixel_offset(pp);
        let mut values = SmallVec::with_capacity(desc.offset.len());

        match &self.pixels {
            PixelStorage::U8(data) => {
                for &c in &desc.offset {
                    let raw = data[pixel_offset + c];
                    values.push(self.device.base.encoding.to_linear_scalar(raw));
                }
            }
            PixelStorage::F16(data) => {
                for &c in &desc.offset {
                    values.push(data[pixel_offset + c].to_f32());
                }
            }
            PixelStorage::F32(data) => {
                for &c in &desc.offset {
                    values.push(data[pixel_offset + c]);
                }
            }
        }

        ImageChannelValues(values)
    }

    pub fn channel_names_from_desc(&self, desc: &ImageChannelDesc) -> Vec<&str> {
        desc.offset
            .iter()
            .map(|&i| self.channel_names[i].as_str())
            .collect()
    }

    pub fn get_channel_desc(
        &self,
        requested_channels: &[impl AsRef<str> + std::fmt::Display],
    ) -> Result<ImageChannelDesc> {
        let mut offset = Vec::with_capacity(requested_channels.len());

        for req in requested_channels.iter() {
            match self.channel_names.iter().position(|n| n == req.as_ref()) {
                Some(idx) => {
                    offset.push(idx);
                }
                None => {
                    return Err(anyhow!(
                        "Missing channel '{}'. Available: {:?}",
                        req,
                        self.channel_names
                    ));
                }
            }
        }

        Ok(ImageChannelDesc { offset })
    }

    pub fn all_channels_desc(&self) -> ImageChannelDesc {
        ImageChannelDesc {
            offset: (0..self.n_channels() as usize).collect(),
        }
    }

    pub fn select_channels(&self, desc: &ImageChannelDesc) -> Self {
        let new_names: Vec<String> = desc
            .offset
            .iter()
            .map(|&i| self.channel_names[i].clone())
            .collect();

        let res = self.resolution();
        let pixel_count = (res.x() * res.y()) as usize;
        let src_nc = self.n_channels() as usize;
        let dst_nc = desc.offset.len();

        let new_storage = match &self.pixels {
            PixelStorage::U8(src) => {
                let mut dst = vec![0u8; pixel_count * dst_nc];
                for i in 0..pixel_count {
                    for (out_idx, &in_c) in desc.offset.iter().enumerate() {
                        dst[i * dst_nc + out_idx] = src[i * src_nc + in_c];
                    }
                }
                PixelStorage::U8(dst)
            }
            PixelStorage::F16(src) => {
                let mut dst = vec![f16::ZERO; pixel_count * dst_nc];
                for i in 0..pixel_count {
                    for (out_idx, &in_c) in desc.offset.iter().enumerate() {
                        dst[i * dst_nc + out_idx] = src[i * src_nc + in_c];
                    }
                }
                PixelStorage::F16(dst)
            }
            PixelStorage::F32(src) => {
                let mut dst = vec![0.0f32; pixel_count * dst_nc];
                for i in 0..pixel_count {
                    for (out_idx, &in_c) in desc.offset.iter().enumerate() {
                        dst[i * dst_nc + out_idx] = src[i * src_nc + in_c];
                    }
                }
                PixelStorage::F32(dst)
            }
        };

        Self::from_storage(new_storage, res, &new_names, self.encoding())
    }

    pub fn get_sampling_distribution<F>(&self, dxd_a: F, domain: Bounds2f) -> Array2D<Float>
    where
        F: Fn(Point2f) -> Float + Sync + Send,
    {
        let width = self.resolution().x();
        let height = self.resolution().y();

        let mut dist: Array2D<Float> = Array2D::new_dims(width, height);

        dist.values
            .par_chunks_mut(width as usize)
            .enumerate()
            .for_each(|(y, row)| {
                let y = y as i32;

                for (x, out_val) in row.iter_mut().enumerate() {
                    let x = x as i32;

                    let value = self.get_channels(Point2i::new(x, y)).average();

                    let u = (x as Float + 0.5) / width as Float;
                    let v = (y as Float + 0.5) / height as Float;
                    let p = domain.lerp(Point2f::new(u, v));
                    *out_val = value * dxd_a(p);
                }
            });

        dist
    }

    pub fn get_sampling_distribution_uniform(&self) -> Array2D<Float> {
        let default_domain = Bounds2f::from_points(Point2f::new(0.0, 0.0), Point2f::new(1.0, 1.0));

        self.get_sampling_distribution(|_| 1.0, default_domain)
    }

    pub fn mse(
        &self,
        desc: ImageChannelDesc,
        ref_img: &Image,
        generate_mse_image: bool,
    ) -> (ImageChannelValues, Option<Image>) {
        let res = self.resolution();

        let mut sum_se: Vec<f64> = vec![0.; desc.size()];
        let names_ref = self.channel_names_from_desc(&desc);
        let ref_desc = ref_img
            .get_channel_desc(&self.channel_names_from_desc(&desc))
            .expect("Channels not found in image");
        assert_eq!(res, ref_img.resolution());

        let width = res.x() as usize;
        let height = res.y() as usize;
        let n_channels = desc.offset.len();
        let mut mse_pixels = if generate_mse_image {
            vec![0.0f32; width * height * n_channels]
        } else {
            Vec::new()
        };

        for y in 0..res.y() {
            for x in 0..res.x() {
                let v =
                    self.get_channels_with_desc(Point2i::new(x, y), &desc, WrapMode::Clamp.into());
                let v_ref = self.get_channels_with_desc(
                    Point2i::new(x, y),
                    &ref_desc,
                    WrapMode::Clamp.into(),
                );
                for c in 0..desc.size() {
                    let se = square(v[c] as f64 - v_ref[c] as f64);
                    if se.is_infinite() {
                        continue;
                    }
                    sum_se[c] += se;
                    if generate_mse_image {
                        let idx = (y as usize * width + x as usize) * n_channels + c;
                        mse_pixels[idx] = se as f32;
                    }
                }
            }
        }

        let pixel_count = (res.x() * res.y()) as f64;
        let mse_values: SmallVec<[Float; 4]> =
            sum_se.iter().map(|&s| (s / pixel_count) as Float).collect();

        let mse_image = if generate_mse_image {
            Some(Image::new(PixelFormat::F32, res, &names_ref, LINEAR.into()))
        } else {
            None
        };

        (ImageChannelValues(mse_values), mse_image)
    }

    pub fn update_view_pointers(&mut self) {
        self.device.pixels = match &self.pixels {
            PixelStorage::U8(vec) => Pixels::U8(vec.as_ptr().into()),
            PixelStorage::F16(vec) => Pixels::F16((vec.as_ptr() as *const f16).into()),
            PixelStorage::F32(vec) => Pixels::F32(vec.as_ptr().into()),
        };
    }

    pub fn has_any_infinite_pixels(&self) -> bool {
        if self.format() == PixelFormat::F32 {
            return false;
        }

        for y in 0..self.resolution().y() {
            for x in 0..self.resolution().x() {
                for c in 0..self.n_channels() {
                    if self.get_channel(Point2i::new(x, y), c).is_infinite() {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    pub fn has_any_nan_pixels(&self) -> bool {
        if self.format() == PixelFormat::F32 {
            return false;
        }

        for y in 0..self.resolution().y() {
            for x in 0..self.resolution().x() {
                for c in 0..self.n_channels() {
                    if self.get_channel(Point2i::new(x, y), c).is_nan() {
                        return true;
                    }
                }
            }
        }
        return false;
    }
}

impl std::ops::Deref for Image {
    type Target = DeviceImage;

    fn deref(&self) -> &DeviceImage {
        &self.device
    }
}
