use crate::paths;
use anyhow::Result;
use exr::prelude::{ReadChannels, ReadLayers};

pub struct DitherTextures
{
    pub texture_array: wgpu::Texture,
    pub view: wgpu::TextureView,
    pub sampler: wgpu::Sampler,
}

pub struct FlowmapTexture
{
    pub texture: wgpu::Texture,
    pub view: wgpu::TextureView,
    pub sampler: wgpu::Sampler,
}

pub struct HeightmapTexture
{
    pub texture: wgpu::Texture,
    pub view: wgpu::TextureView,
    pub sampler: wgpu::Sampler,
}

impl DitherTextures
{
    pub fn load_octaves(device: &wgpu::Device, queue: &wgpu::Queue) -> Result<Self>
    {
        let octave_paths = [
            paths::textures::dither_octave(0),
            paths::textures::dither_octave(1),
            paths::textures::dither_octave(2),
            paths::textures::dither_octave(3),
        ];

        let mut images = Vec::new();
        let mut texture_size = 0;

        for path in &octave_paths
        {
            let img = image::open(&path)?.to_luma8();
            let (width, height) = img.dimensions();

            if texture_size == 0
            {
                texture_size = width;
            }
            else if width != texture_size || height != texture_size
            {
                return Err(anyhow::anyhow!(
                    "All dither textures must be the same size. Expected {}x{}, got {}x{}",
                    texture_size,
                    texture_size,
                    width,
                    height
                ));
            }

            if width != height
            {
                return Err(anyhow::anyhow!(
                    "Dither textures must be square. Got {}x{}",
                    width,
                    height
                ));
            }

            images.push(img);
        }

        let texture_array = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Dither Texture Array"),
            size: wgpu::Extent3d {
                width: texture_size,
                height: texture_size,
                depth_or_array_layers: 4,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::R8Unorm,
            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
            view_formats: &[],
        });

        for (i, img) in images.iter().enumerate()
        {
            queue.write_texture(
                wgpu::TexelCopyTextureInfo {
                    texture: &texture_array,
                    mip_level: 0,
                    origin: wgpu::Origin3d {
                        x: 0,
                        y: 0,
                        z: i as u32,
                    },
                    aspect: wgpu::TextureAspect::All,
                },
                img.as_raw(),
                wgpu::TexelCopyBufferLayout {
                    offset: 0,
                    bytes_per_row: Some(texture_size),
                    rows_per_image: Some(texture_size),
                },
                wgpu::Extent3d {
                    width: texture_size,
                    height: texture_size,
                    depth_or_array_layers: 1,
                },
            );
        }

        let view = texture_array.create_view(&wgpu::TextureViewDescriptor {
            label: Some("Dither Texture Array View"),
            dimension: Some(wgpu::TextureViewDimension::D2Array),
            ..Default::default()
        });

        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("Dither Sampler"),
            address_mode_u: wgpu::AddressMode::Repeat,
            address_mode_v: wgpu::AddressMode::Repeat,
            address_mode_w: wgpu::AddressMode::Repeat,
            mag_filter: wgpu::FilterMode::Nearest,
            min_filter: wgpu::FilterMode::Nearest,
            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
            ..Default::default()
        });

        Ok(Self {
            texture_array,
            view,
            sampler,
        })
    }
}

impl FlowmapTexture
{
    pub fn load(device: &wgpu::Device, queue: &wgpu::Queue, path: &str) -> Result<Self>
    {
        let image = exr::prelude::read()
            .no_deep_data()
            .largest_resolution_level()
            .all_channels()
            .all_layers()
            .all_attributes()
            .from_file(path)?;

        let layer = &image.layer_data[0];
        let width = layer.size.width();
        let height = layer.size.height();

        if width != height
        {
            return Err(anyhow::anyhow!(
                "Flowmap texture must be square. Got {}x{}",
                width,
                height
            ));
        }

        let mut rgba_data: Vec<f32> = vec![1.0; width * height * 4];

        for channel in &layer.channel_data.list
        {
            let channel_name = channel.name.to_string();
            let values: Vec<f32> = channel.sample_data.values_as_f32().collect();

            let target_channel = match channel_name.as_str()
            {
                "R" => 0,
                "G" => 1,
                "B" => 2,
                "A" => 3,
                _ => continue,
            };

            for (i, &value) in values.iter().enumerate()
            {
                rgba_data[i * 4 + target_channel] = value;
            }
        }

        let texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Flowmap Texture"),
            size: wgpu::Extent3d {
                width: width as u32,
                height: height as u32,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::Rgba32Float,
            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
            view_formats: &[],
        });

        queue.write_texture(
            wgpu::TexelCopyTextureInfo {
                texture: &texture,
                mip_level: 0,
                origin: wgpu::Origin3d::ZERO,
                aspect: wgpu::TextureAspect::All,
            },
            bytemuck::cast_slice(&rgba_data),
            wgpu::TexelCopyBufferLayout {
                offset: 0,
                bytes_per_row: Some(16 * width as u32),
                rows_per_image: Some(height as u32),
            },
            wgpu::Extent3d {
                width: width as u32,
                height: height as u32,
                depth_or_array_layers: 1,
            },
        );

        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());

        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("Flowmap Sampler"),
            address_mode_u: wgpu::AddressMode::Repeat,
            address_mode_v: wgpu::AddressMode::Repeat,
            address_mode_w: wgpu::AddressMode::Repeat,
            mag_filter: wgpu::FilterMode::Nearest,
            min_filter: wgpu::FilterMode::Nearest,
            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
            ..Default::default()
        });

        Ok(Self {
            texture,
            view,
            sampler,
        })
    }
}

impl HeightmapTexture
{
    pub fn load(device: &wgpu::Device, queue: &wgpu::Queue, path: &str) -> Result<Self>
    {
        let image = exr::prelude::read()
            .no_deep_data()
            .largest_resolution_level()
            .all_channels()
            .all_layers()
            .all_attributes()
            .from_file(path)?;

        let layer = &image.layer_data[0];
        let width = layer.size.width();
        let height = layer.size.height();

        let channel = &layer.channel_data.list[0];
        let height_data: Vec<f32> = channel.sample_data.values_as_f32().collect();

        let texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("Heightmap Texture"),
            size: wgpu::Extent3d {
                width: width as u32,
                height: height as u32,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: wgpu::TextureFormat::R32Float,
            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
            view_formats: &[],
        });

        queue.write_texture(
            wgpu::TexelCopyTextureInfo {
                texture: &texture,
                mip_level: 0,
                origin: wgpu::Origin3d::ZERO,
                aspect: wgpu::TextureAspect::All,
            },
            bytemuck::cast_slice(&height_data),
            wgpu::TexelCopyBufferLayout {
                offset: 0,
                bytes_per_row: Some(4 * width as u32),
                rows_per_image: Some(height as u32),
            },
            wgpu::Extent3d {
                width: width as u32,
                height: height as u32,
                depth_or_array_layers: 1,
            },
        );

        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());

        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("Heightmap Sampler"),
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            mag_filter: wgpu::FilterMode::Nearest,
            min_filter: wgpu::FilterMode::Nearest,
            ..Default::default()
        });

        Ok(Self {
            texture,
            view,
            sampler,
        })
    }
}
