use anyhow::Result;
use gltf_loader::prelude::*;
use vulkan_rs::prelude::*;

use std::sync::{Arc, Mutex};

pub struct ImageFormatChecker {
    image_data: GltfImageData,
}

impl ImageFormatChecker {
    pub fn new(image_data: GltfImageData) -> ImageFormatChecker {
        ImageFormatChecker { image_data }
    }

    pub fn build(self, device: &Arc<Device>, queue: &Arc<Mutex<Queue>>) -> Result<Arc<Image>> {
        let initial_format = Self::convert_format(self.image_data.format);
        let mut image_builder = Image::from_raw(
            self.image_data.pixels.clone(),
            self.image_data.width,
            self.image_data.height,
        )
        .format(initial_format)
        .max_mip_map_levels()
        .attach_pretty_sampler(device)?;

        let mut format = initial_format;

        loop {
            if image_builder.check_configuration(device) {
                break;
            } else {
                format = match Self::backup_format(format) {
                    Some(format) => format,
                    None => {
                        return Err(anyhow::Error::msg(format!(
                            "Image format {:?} for asset does not work",
                            format
                        )));
                    }
                };

                image_builder = image_builder.format(format);
            }
        }

        if initial_format != format {
            image_builder = image_builder.update_data(Self::convert_data(
                self.image_data.pixels,
                initial_format,
                format,
            ));
        }

        image_builder.build(device, queue)
    }

    fn convert_format(gltf_format: GltfImageFormat) -> VkFormat {
        match gltf_format {
            GltfImageFormat::R8 => VK_FORMAT_R8_UNORM,
            GltfImageFormat::R8G8 => VK_FORMAT_R8G8_UNORM,
            GltfImageFormat::R8G8B8 => VK_FORMAT_R8G8B8_UNORM,
            GltfImageFormat::R8G8B8A8 => VK_FORMAT_R8G8B8A8_UNORM,

            GltfImageFormat::R16 => VK_FORMAT_R16_UNORM,
            GltfImageFormat::R16G16 => VK_FORMAT_R16G16_UNORM,
            GltfImageFormat::R16G16B16 => VK_FORMAT_R16G16B16_UNORM,
            GltfImageFormat::R16G16B16A16 => VK_FORMAT_R16G16B16A16_UNORM,

            GltfImageFormat::R32G32B32FLOAT => VK_FORMAT_R32G32B32_SFLOAT,
            GltfImageFormat::R32G32B32A32FLOAT => VK_FORMAT_R32G32B32A32_SFLOAT,
        }
    }

    fn backup_format(format: VkFormat) -> Option<VkFormat> {
        match format {
            VK_FORMAT_R8_UNORM => Some(VK_FORMAT_R8G8_UNORM),
            VK_FORMAT_R8G8_UNORM => Some(VK_FORMAT_R8G8B8_UNORM),
            VK_FORMAT_R8G8B8_UNORM => Some(VK_FORMAT_R8G8B8A8_UNORM),
            VK_FORMAT_R8G8B8A8_UNORM => None,
            VK_FORMAT_B8G8R8_UNORM => Some(VK_FORMAT_B8G8R8A8_UNORM),
            VK_FORMAT_B8G8R8A8_UNORM => None,
            _ => None,
        }
    }

    fn convert_data(
        data: Vec<u8>,
        source_format: VkFormat,
        destination_format: VkFormat,
    ) -> Vec<u8> {
        let source_step_size = Self::format_size(source_format);
        let destination_step_size = Self::format_size(destination_format);

        let final_size = (data.len() / source_step_size) * destination_step_size;
        let mut dst_buffer = Vec::with_capacity(final_size);

        if source_step_size == 1 {
            if destination_step_size == 2 {
                for d in data {
                    dst_buffer.push(d);
                    dst_buffer.push(0);
                }
            } else if destination_step_size == 3 {
                for d in data {
                    dst_buffer.push(d);
                    dst_buffer.push(0);
                    dst_buffer.push(0);
                }
            } else if destination_step_size == 4 {
                for d in data {
                    dst_buffer.push(d);
                    dst_buffer.push(0);
                    dst_buffer.push(0);
                    dst_buffer.push(255);
                }
            } else {
                panic!("not possible !?");
            }
        } else if source_step_size == 2 {
            if destination_step_size == 3 {
                for i in (0..data.len()).step_by(source_step_size) {
                    dst_buffer.push(data[i]);
                    dst_buffer.push(data[i + 1]);
                    dst_buffer.push(0);
                }
            } else if destination_step_size == 4 {
                for i in (0..data.len()).step_by(source_step_size) {
                    dst_buffer.push(data[i]);
                    dst_buffer.push(data[i + 1]);
                    dst_buffer.push(0);
                    dst_buffer.push(255);
                }
            } else {
                panic!("not possible !?");
            }
        } else if source_step_size == 3 {
            if destination_step_size == 4 {
                for i in (0..data.len()).step_by(source_step_size) {
                    dst_buffer.push(data[i]);
                    dst_buffer.push(data[i + 1]);
                    dst_buffer.push(data[i + 2]);
                    dst_buffer.push(255);
                }
            } else {
                panic!("not possible !?");
            }
        } else {
            panic!("not possible !?");
        };

        if dst_buffer.len() != final_size {
            panic!("something went wrong");
        }

        dst_buffer
    }

    fn format_size(format: VkFormat) -> usize {
        match format {
            VK_FORMAT_R8_UNORM => 1,
            VK_FORMAT_R8G8_UNORM => 2,
            VK_FORMAT_R8G8B8_UNORM => 3,
            VK_FORMAT_R8G8B8A8_UNORM => 4,
            VK_FORMAT_B8G8R8_UNORM => 3,
            VK_FORMAT_B8G8R8A8_UNORM => 4,
            _ => panic!("actually should never panic!"),
        }
    }
}