use std::time::{Duration, Instant};

use pixels::{Error, Pixels, SurfaceTexture};
use rsrt::builder::RaytracerBuilder;
use rsrt::interval::Interval;
use rsrt::raytracer::util::RenderContext;
use rsrt::raytracer::{random_f64, random_f64_range, Raytracer};
use rsrt::vec3::{Color, Point3, Vec3};
use winit::dpi::LogicalSize;
use winit::event::{Event, VirtualKeyCode};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
use winit_input_helper::WinitInputHelper;

const WIDTH: u32 = 1920 / 1;
const HEIGHT: u32 = 1080 / 1;
const SPP_HQ: usize = 100;
const SPP_LQ: usize = 15;
const SPP_SLQ: usize = 1;

struct State {
	pub look_from: Point3,
	pub look_at: Point3,
    pub vfov: f64,
    pub focus: f64,
    pub width: usize,
    pub height: usize,
	builder: RaytracerBuilder,
	render_ctx: RenderContext,
    pub last_render: Instant,
    pub last_slq: bool,
}

pub fn run() -> Result<(), Error> {
	let event_loop = EventLoop::new();
	let mut input = WinitInputHelper::new();
	let window = {
		let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64);
		WindowBuilder::new()
			.with_title("rtrt")
			.with_inner_size(size)
			.with_min_inner_size(size)
			.build(&event_loop)
			.unwrap()
	};

	let mut pixels = {
		let window_size = window.inner_size();
		let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
		Pixels::new(WIDTH, HEIGHT, surface_texture)?
	};
	let mut world = State::new();

	event_loop.run(move |event, _, control_flow| {
        if world.last_slq && world.last_render.elapsed() >= Duration::from_secs(2) {
            world.upgrade_from_slq();
        }

		// Draw the current frame
		if let Event::RedrawRequested(_) = event {
			world.draw(pixels.frame_mut());
			if let Err(err) = pixels.render() {
				tracing::error!("pixels.render: {err}");
				*control_flow = ControlFlow::Exit;
				return;
			}
		}

		// Handle input events
		if input.update(&event) {
			// Close events
			if input.key_pressed(VirtualKeyCode::Escape) || input.close_requested() {
				*control_flow = ControlFlow::Exit;
				return;
			}

			const MOVE_ADJ: f64 = 0.25;
			const LOOK_ADJ: f64 = 0.25;
			const FOV_ADJ: f64 = 0.10;
            const FOCUS_ADJ: f64 = 1.00;


			let mut move_backwards_amount = 0.0f64;
			let mut move_left_amount = 0.0f64;
            let mut move_up_amount = 0.0f64;
			let mut look_up_amount = 0.0f64;
			let mut look_left_amount = 0.0f64;
            let mut fov_amount = 0.0f64;
			let mut hq = false;
            let mut focus_amount = 0.0f64;

			// Move events
			if input.key_pressed(VirtualKeyCode::W) {
				move_backwards_amount -= MOVE_ADJ;
			}

			if input.key_pressed(VirtualKeyCode::S) {
				move_backwards_amount += MOVE_ADJ;
			}

			if input.key_pressed(VirtualKeyCode::A) {
				move_left_amount += MOVE_ADJ;
			}

			if input.key_pressed(VirtualKeyCode::D) {
				move_left_amount -= MOVE_ADJ;
			}

			if input.key_pressed(VirtualKeyCode::Up) {
				look_up_amount += LOOK_ADJ;
			}

			if input.key_pressed(VirtualKeyCode::Down) {
				look_up_amount -= LOOK_ADJ;
			}

			if input.key_pressed(VirtualKeyCode::Right) {
				look_left_amount -= LOOK_ADJ;
			}

			if input.key_pressed(VirtualKeyCode::Left) {
				look_left_amount += LOOK_ADJ;
			}

            if input.key_pressed(VirtualKeyCode::Space) {
                move_up_amount += LOOK_ADJ;
            }

            if input.key_pressed(VirtualKeyCode::LShift) || input.key_pressed(VirtualKeyCode::RShift) {
                move_up_amount -= LOOK_ADJ;
            }

            if input.key_pressed(VirtualKeyCode::Equals) {
                fov_amount += FOV_ADJ;
            }

            if input.key_pressed(VirtualKeyCode::Minus) {
                fov_amount -= FOV_ADJ;
            }

            if input.key_pressed(VirtualKeyCode::LBracket) {
                focus_amount += FOCUS_ADJ;
            }

            if input.key_pressed(VirtualKeyCode::RBracket) {
                focus_amount -= FOCUS_ADJ;
            }


			if input.key_pressed(VirtualKeyCode::Return) {
				hq = true;
			}


            let resized: Option<(usize, usize)> = None;
			// Resize the window
			if let Some(size) = input.window_resized() {
				if let Err(err) = pixels.resize_surface(size.width, size.height) {
					tracing::error!("pixels.resize_surface: {err}");
					*control_flow = ControlFlow::Exit;
					return;
				}

                // if let Err(err) = pixels.resize_buffer(size.width, size.height) {
                //     tracing::error!("pixels.resize_buffer: {err}");
				// 	*control_flow = ControlFlow::Exit;
				// 	return;
                // }

                // resized = Some((size.width, size.height))
			}

			// Update internal state and request a redraw
			if hq
				|| look_left_amount != 0.0
				|| look_up_amount != 0.0
				|| move_backwards_amount != 0.0
				|| move_left_amount != 0.0
                || move_up_amount != 0.0
                || resized.is_some()
                || fov_amount != 0.0
                || focus_amount != 0.0
			{
                if let Some((width, height)) = resized {
                    world.width = width as usize;
                    world.height = height as usize;
                }

                world.focus += focus_amount;
				world.look_at += Vec3::new(0.0, look_up_amount, look_left_amount); //move_backwards_amount, look_up_amount + move_up_amount, look_left_amount + move_left_amount);
				world.look_from += Vec3::new(move_backwards_amount, move_up_amount, move_left_amount);
                world.vfov += fov_amount;
				world.update(hq, resized.is_some());
			}
			window.request_redraw();
		}
	});
}

impl State {
	/// Create a new `World` instance that can draw a moving box.
	fn new() -> Self {
		let look_from = Point3::new(13.0, 2.0, 3.0);
		let look_at = Point3::new(0.0, 0.0, 0.0);
        let vfov = 20.0;
        let focus = 10.0;

		let mut builder = Raytracer::builder()
			.image_size(WIDTH as usize, HEIGHT as usize)
			.samples_per_pixel(1)
			.max_depth(50)
			.vertical_fov(vfov)
			.look_from(13.0, 2.0, 3.0)
			.look_at(0.0, 0.0, 0.0)
			.vup(0.0, 1.0, 0.0)
			.defocus_angle(0.6)
			.focus_dist(focus)
			.with_sphere(0.0, -1000.0, 0.0, 1000.0)
			.lambertian(1.0, 0.6, 0.7);

		for a in -11..11 {
			for b in -11..11 {
				let choose_mat = random_f64();
				let center =
					Point3::new(a as f64 + 0.9 * random_f64(), 0.2, b as f64 + 0.9 * random_f64());

				if (center - Point3::new(4.0, 0.2, 0.0)).len() > 0.9 {
					let shape_builder =
						builder.with_sphere(center.x(), center.y(), center.z(), 0.2);

					builder = if choose_mat < 0.8 {
						let albedo = Color::random() * Color::random();
						shape_builder.lambertian(albedo.r(), albedo.g(), albedo.b())
					} else if choose_mat < 0.95 {
						let albedo = Color::random_range(0.5, 1.0);
						let fuzz = random_f64_range(0.0, 0.5);
						shape_builder.metal(albedo.r(), albedo.g(), albedo.b(), fuzz)
					} else {
						shape_builder.dielectric(1.5)
					};
				}
			}
		}

		let builder = builder
			.with_sphere(0.0, 1.0, 0.0, 1.0)
			.dielectric(1.5)
			.with_sphere(-4.0, 1.0, 0.0, 1.0)
			.lambertian(1.0, 0.5, 0.6)
			.with_sphere(4.0, 1.0, 0.0, 1.0)
			.metal(0.7, 0.6, 0.5, 0.0);

        let render_ctx = RenderContext::new(WIDTH as usize, HEIGHT as usize, 0);
        builder.clone().finish().render_multithread_rayon_detached(render_ctx.clone());
		Self {
			builder: builder.clone(),
			look_from,
			look_at,
			render_ctx,
            width: WIDTH as usize,
            height: HEIGHT as usize,
            last_render: Instant::now(),
            last_slq: true,
            vfov,
            focus
		}
	}

    fn upgrade_from_slq(&mut self) {
        assert!(self.last_slq);
        self.last_slq = false;
        self.builder
			.samples_per_pixel_ref(SPP_LQ);
		self.builder.clone().finish().render_multithread_rayon_detached(self.render_ctx.clone());
    }

	fn update(&mut self, hq: bool, changed_buffer: bool) {
        self.render_ctx.next();

        if changed_buffer {
            self.render_ctx = RenderContext::new(self.width, self.height, self.render_ctx.render_id());
            self.builder.image_size_ref(self.width, self.height);
        }

        let spp = match hq {
            true => SPP_HQ,
            false => SPP_SLQ,
        };

        self.last_render = Instant::now();
        self.last_slq = spp == SPP_SLQ;

		self.builder
            .focus_dist_ref(self.focus)
            .vertical_fov_ref(self.vfov)
			.samples_per_pixel_ref(match hq {
				true => SPP_HQ,
				false => SPP_SLQ,
			})
			.look_from_ref(self.look_from.x(), self.look_from.y(), self.look_from.z())
			.look_at_ref(self.look_at.x(), self.look_at.y(), self.look_at.z());
		self.builder.clone().finish().render_multithread_rayon_detached(self.render_ctx.clone());
	}

	fn draw(&mut self, frame: &mut [u8]) {
		for (index, pixel) in frame.chunks_exact_mut(4).enumerate() {
			fn linear_to_gamma(linear_component: f64) -> f64 {
				if linear_component > 0.0 {
					return linear_component.sqrt();
				}

				0.0
			}

            let color = self.render_ctx.get_color_raw_idx(index);

			let r = linear_to_gamma(color.r());
			let g = linear_to_gamma(color.g());
			let b = linear_to_gamma(color.b());

			const INTENSITY: Interval = Interval::new(0.000, 0.999);

			pixel.copy_from_slice(&[
				(INTENSITY.clamp(r) * 256.0) as u8,
				(INTENSITY.clamp(g) * 256.0) as u8,
				(INTENSITY.clamp(b) * 256.0) as u8,
				255 as u8,
			]);
		}
	}
}
