use std::fs::File;
use std::io::BufReader;
use std::path::Path;

use itertools::Itertools;
use tiff::decoder::Decoder;
use wgpu::Device;
use wgpu::RenderPass;
use wgpu::TextureFormat;

use crate::state::Camera;
use crate::voxel_renderer::VoxelRenderer;

pub struct HeightMapRenderer
{
    width: u32,
    height: u32,

    heightmap: Vec<f32>,
    height_min: f32,
    height_max: f32,

    alive_chunks: Vec<cgmath::Vector3<i32>>,
    eye_pos: cgmath::Vector3<f32>,

    voxel_renderer: VoxelRenderer,
}

impl HeightMapRenderer
{
    pub fn from_image(
        path: impl AsRef<Path>,
        device: &Device,
        surface_format: TextureFormat,
    ) -> Self
    {
        let file = File::open(path).unwrap();
        let mut decoder = Decoder::new(BufReader::new(file)).unwrap();
        let (width, height) = decoder.dimensions().unwrap();

        let decoded = decoder.read_image().unwrap();
        let pixels = match decoded
        {
            tiff::decoder::DecodingResult::F32(decoded_pixels) => decoded_pixels,
            _ =>
            {
                panic!("Unsuported image format.");
            }
        };

        Self {
            width,
            height,

            height_min: pixels.iter().copied().reduce(f32::min).unwrap_or(0.),
            height_max: pixels.iter().copied().reduce(f32::max).unwrap_or(0.),

            heightmap: pixels,
            alive_chunks: vec![],
            eye_pos: cgmath::Vector3::new(0., 0., 0.),

            voxel_renderer: VoxelRenderer::new(device, surface_format),
        }
    }

    pub fn set_eye_pos(&mut self, eye_pos: cgmath::Vector3<f32>, device: &Device)
    {
        self.eye_pos = eye_pos;
        // self.voxel_renderer.set_chunks(
        //     device,
        //     &[
        //         cgmath::Vector3::new(-1, 0, -1),
        //         cgmath::Vector3::new(-1, 0, 1),
        //         cgmath::Vector3::new(1, 0, -1),
        //         cgmath::Vector3::new(1, 0, 1),
        //     ],
        // );
        // return;
        // Compute alive chunks

        let eye_chunk = cgmath::Vector3::new(
            eye_pos.x.floor() as i32,
            eye_pos.y.floor() as i32,
            eye_pos.z.floor() as i32,
        );
        let mut alive_chunks = vec![];
        for ((x, y), z) in (-10..=10)
            .cartesian_product(-10..=10)
            .cartesian_product(-10..=10)
        {
            let chunk = eye_chunk + cgmath::Vector3::new(x, y, z);
            if chunk.x >= 0 && chunk.z >= 0
            {
                if chunk.y == -1
                {
                    alive_chunks.push(chunk);
                }
            }
        }

        self.alive_chunks = alive_chunks;
        if !self.alive_chunks.is_empty()
        {
            self.voxel_renderer.set_chunks(device, &self.alive_chunks);
        }
        return;

        let mut alive_chunks = vec![];
        for ((x, y), z) in (-10..=10)
            .cartesian_product(-10..=10)
            .cartesian_product(-10..=10)
        {
            let chunk = eye_chunk + cgmath::Vector3::new(x, y, z);
            if chunk.x >= 0
                && chunk.x < (self.width / 256) as i32
                && chunk.z >= 0
                && chunk.z < (self.height / 256) as i32
            {
                if chunk.y == -1
                {
                    alive_chunks.push(chunk);
                    println!("{}, {}, {}", chunk.x, chunk.y, chunk.z);
                }
                continue;

                let chunk_voxel_height = y * 256;
                let submit = [
                    (chunk.x, chunk.z),
                    (chunk.x + 1, chunk.z),
                    (chunk.x, chunk.z + 1),
                    (chunk.x + 1, chunk.z + 1),
                ]
                .iter()
                .map(|(chunk_x, chunk_z)| {
                    let voxel_x = chunk_x * 256;
                    let voxel_z = chunk_z * 256;

                    let altitude =
                        self.heightmap[voxel_x as usize + voxel_z as usize * self.width as usize];
                    let voxel_height =
                        map(altitude, self.height_min, self.height_max, 0., 100. * 256.);
                    //println!("{}", voxel_height);
                    (chunk_voxel_height as f32) < voxel_height
                })
                .reduce(|a, b| a || b)
                .unwrap_or(false);
                if submit
                {
                    alive_chunks.push(chunk);
                }
            }
        }
    }

    pub fn render(&mut self, render_pass: &mut RenderPass, camera: &Camera)
    {
        if !self.alive_chunks.is_empty()
        {}
        self.voxel_renderer.render(render_pass, camera);
    }
}

fn map(x: f32, x_min: f32, x_max: f32, y_min: f32, y_max: f32) -> f32
{
    ((x - x_min) / (x_max - x_min)) * (y_max - y_min) + y_min
}
