/* camera.c */

#include <stdlib.h>

#include "config.h"
#include "camera.h"
#include "thread.h"

ray view_eye_ray(view *view, u32 col, u32 row) {

	/* FIXME: tidy this up */
	
	tuple upward = {0.0, 1.0, 0.0};
	tuple n = geo_unit(geo_sub(view->eye_point, view->look_at));
	num w_p = 2 * tan( (PI / 180) * (view->fov / 2) );
	num w_h = w_p * ((num)view->width / (num)view->height);
	tuple u = geo_unit(geo_cross(upward, n));
	tuple v = geo_cross(n, u);	
	tuple C = geo_sub(view->eye_point, n);	
	num   y = ((view->height - row - 0.5) / view->height);
	num   x = (col + 0.5) / view->width;	
	
	tuple P = geo_add(C, geo_add(
		geo_scale(u, w_h * (x - 0.5)), geo_scale(v, w_p * (y - 0.5))
	));
	
	ray r = {view->eye_point, geo_unit(geo_sub(P, view->eye_point))};
	return r;
}

tuple colour_interpolate(tuple a, tuple b, tuple c, tuple d) {
	tuple ret;
	ret.a = (a.a + b.a + c.a + d.a) / 4.0;
	ret.b = (a.b + b.b + c.b + d.b) / 4.0;
	ret.c = (a.c + b.c + c.c + d.c) / 4.0;
	return ret;
}

void camera_paint_thread_entry(void* vp_data) {
	paint_thread_data* data = (paint_thread_data*) vp_data;
	
	/* Determine row range to trace */

	u32 buff_height = (data->v->height) / THREADCOUNT;
	u32 buff_size   = buff_height * data->v->width;
	u32 row_start   = buff_height * data->slice;
	u32 row_end     = buff_height * (data->slice + 1);	
	
#ifndef ENABLE_FSAA
	
	/* Trace rows to global canvas */
	
	u32 _r, _c, _p = buff_size * data->slice;
	for (_r = row_start; _r < row_end; _r++) {
		for (_c = 0; _c < data->v->width; _c++, _p++) {		
			ray r = view_eye_ray(data->v, _c, _r);
			data->canvas[_p] = geo_as_u32(camera_trace(data->c,r, REFLECTIONS));
		}
	}
	
#else

	tuple *buff = (tuple*) malloc(sizeof(tuple) * buff_size);
	
	/* Trace rows to local buffer */
	
	u32 _r, _c, _p = 0;
	for (_r = row_start; _r < row_end; _r++) {
		for (_c = 0; _c < data->v->width; _c++, _p++) {
			ray r = view_eye_ray(data->v, _c, _r);
			buff[_p] = camera_trace(data->c, r, REFLECTIONS);
		}
	}
	
	/* Apply 4xFSAA linear decimation */
	
	_p = (data->v->height * data->v->width * data->slice) / (4 * THREADCOUNT);
	for (_r = 0; _r < buff_height; _r += 2) {
		for (_c = 0; _c < data->v->width; _c += 2, _p++) {
		
			data->canvas[_p] = geo_as_u32(
				colour_interpolate(
					buff[_r     * data->v->width + _c    ],
					buff[_r     * data->v->width + _c + 1],
					buff[(_r+1) * data->v->width + _c    ], 
					buff[(_r+1) * data->v->width + _c + 1]
				)
			);
			
		}
	}
	
	/* Tidy up */
	
	free(buff);
	
#endif
}

void camera_paint(camera* c, u32* canvas) {
	int i;
	thread_t threads[THREADCOUNT] = {0};
	paint_thread_data data[THREADCOUNT];
	
	/* Delegate work to threads */
	
	for (i = 0; i < THREADCOUNT; i++) {
		data[i].c = c;
		data[i].v = c->v;
		data[i].canvas = canvas;
		data[i].slice = i;
		threads[i] = thread_new(camera_paint_thread_entry, &data[i]);
		thread_start(threads[i]);
	}
	
	/* Wait for all threads to complete */
	
	for (i = 0; i < THREADCOUNT; i++) {
		thread_join(threads[i]);
		thread_free(threads[i]);
	}
}
