let make_point : float -> float -> float -> float * float * float =
	fun (x : float) (y : float) (z : float) -> 
		(x, y, z)
in

let point_x : float * float * float -> float =
	fun (tup : float * float * float) ->
		tup.(0)
in

let point_y : float * float * float -> float =
	fun (tup : float * float * float) ->
		tup.(1)
in

let point_z : float * float * float -> float =
	fun (tup : float * float * float) ->
		tup.(2)
in

let sq : float -> float =
	fun (x : float) ->
		x *. x
in

let mag : float -> float -> float -> float =
	fun (x : float) (y : float) (z : float) ->
		sqrt ((sq x) +. (sq y) +. (sq z))
in

let unit_vector : float -> float -> float -> float * float * float =
	fun (x: float) (y: float) (z: float) ->
		let d : float = mag x y z
		in
		make_point (x /. d) (y /. d) (z /. d)
in

let distance : float * float * float -> float * float * float -> float =
	fun (p1: float * float * float) (p2: float * float * float) ->
		mag (point_x (p1) -. point_x (p2))
		    (point_y (p1) -. point_y (p2))
		    (point_z (p1) -. point_z (p2))
in

let make_sphere : float -> float -> float * float * float -> float * float * (float * float * float) =
	fun (color : float) (radius : float) (center : float * float * float) ->
		(color, radius, center)
in

let sphere_color : float * float * (float * float * float) -> float =
	fun (s : float * float * (float * float * float)) ->
		s.(0)
in

let sphere_radius : float * float * (float * float * float) -> float =
	fun (s : float * float * (float * float * float)) ->
		s.(1)
in

let sphere_center : float * float * (float * float * float) -> float * float * float =
	fun (s : float * float * (float * float * float)) ->
		s.(2)
in

let sphere_normal : float * float * (float * float * float) -> float * float * float -> float * float * float =
	fun (s : float * float * (float * float * float)) (pt : float * float * float) ->
		let c : float * float * float = sphere_center s in
		unit_vector ((point_x c) -. (point_x pt)) ((point_y c) -. (point_y pt)) ((point_z c) -. (point_z pt))
in

let lambert : float * float * (float * float * float) -> float * float * float -> float * float * float -> float =
	fun (s : float * float * (float * float * float)) (i : float * float * float) (ray : float * float * float) ->
		let n : float * float * float = sphere_normal s i in
		float_max 0.0 ((point_x ray) *. (point_x n) +.
			  (point_y ray) *. (point_y n) +.
			  (point_z ray) *. (point_z n))
in

let world : vec[float * float * (float * float * float)] =
	vector 33 (0.0, 0.0, (0.0, 0.0, 0.0))
in

let eye: float * float * float =
	make_point 0.0 0.0 200.0
in

let defsphere : int -> float -> float -> float -> float -> float -> float * float * (float * float * float) =
	fun (i : int) (x : float) (y : float) (z : float) (r : float) (c : float) ->
		let s : float * float * (float * float * float) = make_sphere c r (make_point x y z) in
		vector_set world i s;
		s
in

let rec loop : (float * float * float) -> (float * float * float) -> int -> int -> vec[float * float * (float * float * float)] -> float * float * (float * float * float) -> float * float * float -> float -> (float * float * (float * float * float)) * (float * float * float) =
	fun (pt : float * float * float) (ray : float * float * float) (index : int) (lst_len : int) (lst : vec[float * float * (float * float * float)]) (surface : float * float * (float * float * float)) (hit : float * float * float) (dist : float) ->
	if (index = lst_len) then
		(surface, hit)
	else
		let s : float * float * (float * float * float) = vector_get lst index in
		let xr : float = point_x ray in
		let yr : float = point_y ray in
		let zr : float = point_z ray in
		let sc : float * float * float = sphere_center s in
		let a : float = (sq xr) +. (sq yr) +. (sq zr) in
		let b : float = 2.0 *. (
				(((point_x pt) -. (point_x sc)) *. xr) +.
				(((point_y pt) -. (point_y sc)) *. yr) +.
				(((point_z pt) -. (point_z sc)) *. zr)
				) in
		let c : float = (sq ((point_x pt) -. (point_x sc))) +.
				(sq ((point_y pt) -. (point_y sc))) +.
				(sq ((point_z pt) -. (point_z sc))) +.
				(float_negate (sq (sphere_radius s))) in
		if (a =. 0.0) then
			let n : float = (float_negate c) /. b in
			let h : float * float * float = make_point ((point_x pt) +. (n *. xr)) ((point_y pt) +. (n *. yr)) ((point_z pt) +. (n *. zr)) in
			let d : float = distance h pt in
			if (d <. dist) then
				loop pt ray (index + 1) lst_len lst s h d
			else
				loop pt ray (index + 1) lst_len lst surface hit dist

		else
			let disc : float = (sq b) -. (4.0 *. (a *. c)) in
			if (disc <. 0.0) then
				loop pt ray (index + 1) lst_len lst surface hit dist
			else
				let discrt : float = sqrt disc in
				let minus_b : float = float_negate b in
				let two_a : float = 2.0 *. a in
				let n : float = float_min ((minus_b +. discrt) /. two_a) ((minus_b -. discrt) /. two_a) in
				let h : float * float * float = make_point ((point_x pt) +. (n *. xr)) ((point_y pt) +. (n *. yr)) ((point_z pt) +. (n *. zr)) in
				let d : float = distance h pt in
				if (d <. dist) then
					loop pt ray (index + 1) lst_len lst s h d
				else
					loop pt ray (index + 1) lst_len lst surface hit dist
in

let sendray : float * float * float -> float * float * float -> float =
	fun (pt : float * float * float) (ray : float * float * float) ->
		let x : ((float * float * (float * float * float)) * (float * float * float)) = 
			loop pt ray 0 (vector_length world) world (0.0, 0.0, (0.0, 0.0, 0.0)) (0.0, 0.0, 0.0) 1.0e308
		in
		let s : (float * float * (float * float * float)) =
			x.(0)
		in
		let i : (float * float * float) =
			x.(1)
		in
		(lambert s i ray) *. sphere_color s
in

let color_at: float -> float -> int =
	fun (x : float) (y : float) ->
		let ray: float * float * float =
		unit_vector (x -. (point_x eye))
			    (y -. (point_y eye))
			    (float_negate (point_z eye))
		in
		int_of_float (round ((sendray eye ray) *. 255.0))
in

let tracer : int -> int =
	fun (res : int) ->
		let extent : int = res * 100 in
		for y = 0 to extent do
			for x = 0 to extent do
				let color : int = (color_at (-50.0 +. ((float_of_int x) /. (float_of_int res))) (-50.0 +. ((float_of_int y) /. (float_of_int res)))) in
				if x = (extent - 1) && y = (extent - 1) then
					print_int color ;
					print_char '\n' ;
					0
				else
					0
			done
		done;
		0
in

let run_benchmark : int -> int =
	fun (_ : int) ->
		let res : int = read_int () in
		defsphere 32 0.0 -300.0 -1200.0 200.0 0.8;
		defsphere 31 -80.0 -150.0 -1200.0 200.0 0.7;
		defsphere 30 70.0 -100.0 -1200.0 200.0 0.9;
		(let counter : vec[int] = vector 1 29 in
		for x = -2 to 3 do
			for z = 2 to 8 do
				defsphere (vector_get counter 0) ((float_of_int x) *. 200.0) 300.0 ((float_of_int z) *. -400.0) 40.0 0.75;
				vector_set counter 0 ((vector_get counter 0) - 1)
			done
		done);
		tracer res
in

time run_benchmark
