/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005
 *					All rights reserved
 *
 *  This file is part of GPAC / 3D rendering plugin
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */

#include "render3d_nodes.h"

static void camera_changed(Render3D *sr, M4Camera *cam)
{
	cam->flags |= CAM_IS_DIRTY;
	SR_Invalidate(sr->compositor, NULL);
}

/*shortcut*/
void m4_mx_rotation_matrix(M4Matrix *mx, SFVec3f axis_pt, SFVec3f axis, Fixed angle)
{
	m4_mx_init(*mx);
	m4_mx_add_translation(mx, axis_pt.x, axis_pt.y, axis_pt.z);
	m4_mx_add_rotation(mx, angle, axis.x, axis.y, axis.z);
	m4_mx_add_translation(mx, -axis_pt.x, -axis_pt.y, -axis_pt.z);
}
	
void view_orbit_x(Render3D *sr, M4Camera *cam, Fixed dx)
{
	M4Matrix mx;
	if (!dx) return;
	m4_mx_rotation_matrix(&mx, cam->target, cam->up, dx);
	m4_mx_apply_vec(&mx, &cam->position);
	camera_changed(sr, cam);
}
void view_orbit_y(Render3D *sr, M4Camera *cam, Fixed dy)
{
	M4Matrix mx;
	SFVec3f axis;
	if (!dy) return;
	axis = camera_get_right_dir(cam);
	m4_mx_rotation_matrix(&mx, cam->target, axis, dy);
	m4_mx_apply_vec(&mx, &cam->position);
	/*update up vector*/
	cam->up = m4_vec_cross(camera_get_pos_dir(cam), axis);
	m4_vec_norm(&cam->up);
	camera_changed(sr, cam);
}

void view_exam_x(Render3D *sr, M4Camera *cam, Fixed dx)
{
	M4Matrix mx;
	if (!dx) return;
	m4_mx_rotation_matrix(&mx, cam->examine_center, cam->up, dx);
	m4_mx_apply_vec(&mx, &cam->position);
	m4_mx_apply_vec(&mx, &cam->target);
	camera_changed(sr, cam);
}

void view_exam_y(Render3D *sr, M4Camera *cam, Fixed dy)
{
	M4Matrix mx;
	SFVec3f axis;
	if (!dy) return;
	axis = camera_get_right_dir(cam);
	m4_mx_rotation_matrix(&mx, cam->examine_center, axis, dy);
	m4_mx_apply_vec(&mx, &cam->position);
	m4_mx_apply_vec(&mx, &cam->target);
	/*update up vector*/
	cam->up = m4_vec_cross(camera_get_pos_dir(cam), axis);
	m4_vec_norm(&cam->up);
	camera_changed(sr, cam);
}

void view_roll(Render3D *sr, M4Camera *cam, Fixed dd)
{
	M4Matrix mx;
	SFVec3f delta;
	if (!dd) return;
	m4_vec_add(delta, cam->target, cam->up);
	m4_mx_rotation_matrix(&mx, cam->target, camera_get_pos_dir(cam), dd);
	m4_mx_apply_vec(&mx, &delta);
	m4_vec_diff(cam->up, delta, cam->target);
	m4_vec_norm(&cam->up);
	camera_changed(sr, cam);
}

void view_pan_x(Render3D *sr, M4Camera *cam, Fixed dx)
{
	M4Matrix mx;
	if (!dx) return;
	m4_mx_rotation_matrix(&mx, cam->position, cam->up, dx);
	m4_mx_apply_vec(&mx, &cam->target);
	camera_changed(sr, cam);
}
void view_pan_y(Render3D *sr, M4Camera *cam, Fixed dy)
{
	M4Matrix mx;
	SFVec3f axis;
	if (!dy) return;
	axis = camera_get_right_dir(cam);
	m4_mx_rotation_matrix(&mx, cam->position, axis, dy);
	m4_mx_apply_vec(&mx, &cam->target);
	/*update up vector*/
	cam->up = m4_vec_cross(camera_get_pos_dir(cam), axis);
	m4_vec_norm(&cam->up);
	camera_changed(sr, cam);
}

/*for translation moves when jumping*/
#define JUMP_SCALE_FACTOR	4

void view_translate_x(Render3D *sr, M4Camera *cam, Fixed dx)
{
	SFVec3f v;
	if (!dx) return;
	if (cam->jumping) dx *= JUMP_SCALE_FACTOR;
	v = m4_vec_scale(camera_get_right_dir(cam), dx);
	m4_vec_add(cam->target, cam->target, v);
	m4_vec_add(cam->position, cam->position, v);
	camera_changed(sr, cam);
}
void view_translate_y(Render3D *sr, M4Camera *cam, Fixed dy)
{
	SFVec3f v;
	if (!dy) return;
	if (cam->jumping) dy *= JUMP_SCALE_FACTOR;
	v = m4_vec_scale(cam->up, dy);
	m4_vec_add(cam->target, cam->target, v);
	m4_vec_add(cam->position, cam->position, v);
	camera_changed(sr, cam);
}
void view_translate_z(Render3D *sr, M4Camera *cam, Fixed dz)
{
	SFVec3f v;
	if (!dz) return;
	if (cam->jumping) dz *= JUMP_SCALE_FACTOR;
	v = m4_vec_scale(camera_get_target_dir(cam), dz);
	m4_vec_add(cam->target, cam->target, v);
	m4_vec_add(cam->position, cam->position, v);
	camera_changed(sr, cam);
}

void view_zoom(Render3D *sr, M4Camera *cam, Fixed z)
{
	Fixed oz;
	if ((z>FIX_ONE) || (z<-FIX_ONE)) return;
	oz = m4_divfix(cam->vp_fov, cam->fieldOfView);
	if (oz<FIX_ONE) z/=4; oz += z;
	if (oz<=0) return;

	cam->fieldOfView = m4_divfix(cam->vp_fov, oz);
	if (cam->fieldOfView>M4_PI) cam->fieldOfView=M4_PI;
	camera_changed(sr, cam);
}

static Bool R3D_HandleEvents3D(Render3D *sr, M4UserEvent *ev)
{
	Fixed x, y, trans_scale;
	Fixed dx, dy, key_trans, key_pan, key_exam;
	s32 key_inv;
	u32 keys;
	Bool is_pixel_metrics;
	M4Camera *cam;

	if (sr->active_layer) {
		cam = l3d_get_camera(sr->active_layer);
		is_pixel_metrics = SG_UsePixelMetrics(Node_GetParentGraph(sr->active_layer));
	} else {
		cam = &sr->surface->camera;
		is_pixel_metrics = SG_UsePixelMetrics(sr->compositor->scene);
	}

	if (!cam->navigate_mode) return 0;
	keys = sr->compositor->key_states;
	x = y = 0;
	/*renorm between -1, 1*/
	if (ev->event_type<=M4E_MOUSEWHEEL) {
		x = m4_divfix( INT2FIX(ev->mouse.x + (s32) sr->surface->width/2), INT2FIX(sr->surface->width));
		y = m4_divfix( INT2FIX(ev->mouse.y + (s32) sr->surface->height/2), INT2FIX(sr->surface->height));
	}

	dx = (x - sr->grab_x); 
	dy = (y - sr->grab_y);

	trans_scale = is_pixel_metrics ? cam->width/2 : INT2FIX(10);
	key_trans = is_pixel_metrics ? INT2FIX(10) : cam->avatar_size.x;

	key_pan = FIX_ONE/25;
	key_exam = FIX_ONE/10;
	key_inv = 1;

	if (keys & M4KM_SHIFT) {
		dx *= 4;
		dy *= 4;
		key_pan *= 4;
		key_exam *= 4;
		key_trans*=4;
	}

	switch (ev->event_type) {
	case M4E_LEFTDOWN:
		sr->grab_x = x;
		sr->grab_y = y;
		sr->nav_is_grabbed = 1;
		break;
	case M4E_RIGHTDOWN:
		if (sr->nav_is_grabbed && (cam->navigate_mode==M4_NavWalk)) {
			camera_jump(cam);
			SR_Invalidate(sr->compositor, NULL);
			return 1;
		}
		break;

	/* note: shortcuts are mostly the same as blaxxun contact, I don't feel like remembering 2 sets...*/
	case M4E_MOUSEMOVE:
		if (!sr->nav_is_grabbed) {
			if (cam->navigate_mode==M4_NavGame) {
				/*init mode*/
				sr->grab_x = x;
				sr->grab_y = y;
				sr->nav_is_grabbed = 1;
			}
			return 0;
		}

		switch (cam->navigate_mode) {
		/*FIXME- we'll likely need a "step" value for walk at some point*/
		case M4_NavWalk:
		case M4_NavFly:
			view_pan_x(sr, cam, -dx);
			if (keys & M4KM_CTRL) view_pan_y(sr, cam, dy);
			else view_translate_z(sr, cam, m4_mulfix(dy, trans_scale));
			break;
		case M4_NavVR:
			view_pan_x(sr, cam, -dx);
			if (keys & M4KM_CTRL) view_zoom(sr, cam, dy);
			else view_pan_y(sr, cam, dy);
			break;
		case M4_NavPan:
			view_pan_x(sr, cam, -dx);
			if (keys & M4KM_CTRL) view_translate_z(sr, cam, m4_mulfix(dy, trans_scale));
			else view_pan_y(sr, cam, dy);
			break;
		case M4_NavSlide:
			view_translate_x(sr, cam, m4_mulfix(dx, trans_scale));
			if (keys & M4KM_CTRL) view_translate_z(sr, cam, m4_mulfix(dy, trans_scale));
			else view_translate_y(sr, cam, m4_mulfix(dy, trans_scale));
			break;
		case M4_NavExamine:
			if (keys & M4KM_CTRL) {
				view_translate_z(sr, cam, m4_mulfix(dy, trans_scale));
			} else {
				view_exam_x(sr, cam, -m4_mulfix(M4_PI, dx));
				view_exam_y(sr, cam, m4_mulfix(M4_PI, dy));
			}
			break;
		case M4_NavOrbit:
			if (keys & M4KM_CTRL) {
				view_translate_z(sr, cam, m4_mulfix(dy, trans_scale));
			} else {
				view_orbit_x(sr, cam, -m4_mulfix(M4_PI, dx));
				view_orbit_y(sr, cam, m4_mulfix(M4_PI, dy));
			}
			break;
		case M4_NavGame:
			view_pan_x(sr, cam, -dx);
			view_pan_y(sr, cam, dy);
			break;
		}
		sr->grab_x = x;
		sr->grab_y = y;
		return 1;

	case M4E_MOUSEWHEEL:
		switch (cam->navigate_mode) {
		/*FIXME- we'll likely need a "step" value for walk at some point*/
		case M4_NavWalk:
		case M4_NavFly:
			view_pan_y(sr, cam, m4_mulfix(key_pan, ev->mouse.wheel_pos));
			break;
		case M4_NavVR:
			view_zoom(sr, cam, m4_mulfix(key_pan, ev->mouse.wheel_pos));
			break;
		case M4_NavSlide:
		case M4_NavExamine:
		case M4_NavOrbit:
		case M4_NavPan:
			if (is_pixel_metrics) {
				view_translate_z(sr, cam, m4_mulfix(trans_scale, ev->mouse.wheel_pos) * ((keys & M4KM_SHIFT) ? 4 : 1));
			} else {
				view_translate_z(sr, cam, ev->mouse.wheel_pos * ((keys & M4KM_SHIFT) ? 4 : 1));
			}
			break;
		}
		return 1;

	case M4E_LEFTUP:
		sr->nav_is_grabbed = 0;
		break;

	case M4E_KEYDOWN:
		if (ev->key.virtual_code=='\b') {
			SR_ResetGraphics(sr->compositor);
			return 1;
		}
		else if ((ev->key.virtual_code=='c') || (ev->key.virtual_code=='C')) {
			sr->collide_mode = sr->collide_mode  ? M4_CollideNone : M4_CollideDisp;
			return 1;
		}
		else if ((ev->key.virtual_code=='j') || (ev->key.virtual_code=='J')) {
			if (cam->navigate_mode==M4_NavWalk) {
				camera_jump(cam);
				SR_Invalidate(sr->compositor, NULL);
				return 1;
			}
		}
		break;
	case M4E_VKEYDOWN:
		switch (ev->key.m4_vk_code) {
		case M4VK_HOME:
			if (!sr->nav_is_grabbed) R3D_ResetCamera(sr);
			break;
		case M4VK_END:
			if (cam->navigate_mode==M4_NavGame) {
				cam->navigate_mode = M4_NavWalk;
				sr->nav_is_grabbed = 0;
				return 1;
			}
			break;
		case M4VK_LEFT: key_inv = -1;
		case M4VK_RIGHT:
			switch (cam->navigate_mode) {
			case M4_NavSlide:
				if (keys & M4KM_CTRL) view_pan_x(sr, cam, key_inv * key_pan);
				else view_translate_x(sr, cam, key_inv * key_trans);
				break;
			case M4_NavExamine:
				if (keys & M4KM_CTRL) view_translate_x(sr, cam, key_inv * key_trans);
				else view_exam_x(sr, cam, -key_inv * key_exam);
				break;
			case M4_NavOrbit:
				if (keys & M4KM_CTRL) view_translate_x(sr, cam, key_inv * key_trans);
				else view_orbit_x(sr, cam, -key_inv * key_exam);
				break;
			case M4_NavGame: view_translate_x(sr, cam, key_inv * key_trans); break;
			case M4_NavVR: view_pan_x(sr, cam, -key_inv * key_pan); break;
			/*walk/fly/pan*/
			default:
				if (keys & M4KM_CTRL) view_translate_x(sr, cam, key_inv * key_trans);
				else view_pan_x(sr, cam, -key_inv * key_pan);
				break;
			}
			return 1;
		case M4VK_DOWN: key_inv = -1;
		case M4VK_UP:
			switch (cam->navigate_mode) {
			case M4_NavSlide:
				if (keys & M4KM_CTRL) view_translate_z(sr, cam, key_inv * key_trans);
				else view_translate_y(sr, cam, key_inv * key_trans);
				break;
			case M4_NavExamine:
				if (keys & M4KM_CTRL) view_translate_y(sr, cam, key_inv * key_trans);
				else view_exam_y(sr, cam, -key_inv * key_exam);
				break;
			case M4_NavOrbit:
				if (keys & M4KM_CTRL) view_translate_y(sr, cam, key_inv * key_trans);
				else view_orbit_y(sr, cam, -key_inv * key_exam);
				break;
			case M4_NavPan:
				if (keys & M4KM_CTRL) view_translate_y(sr, cam, key_inv * key_trans);
				else view_pan_y(sr, cam, key_inv * key_pan);
				break;
			case M4_NavGame: view_translate_z(sr, cam, key_inv * key_trans); break;
			case M4_NavVR:
				if (keys & M4KM_CTRL) view_zoom(sr, cam, key_inv * key_pan);
				else view_pan_y(sr, cam, key_inv * key_pan);
				break;
			/*walk/fly*/
			default:
				if (keys & M4KM_CTRL) view_pan_y(sr, cam, key_inv * key_pan);
				else view_translate_z(sr, cam, key_inv * key_trans);
				break;
			}
			return 1;

		case M4VK_NEXT:
			if (keys & M4KM_CTRL) { view_zoom(sr, cam, FIX_ONE/10); return 1; }
			break;
		case M4VK_PRIOR:
			if (keys & M4KM_CTRL) { view_zoom(sr, cam, -FIX_ONE/10); return 1; }
			break;
		}
		break;
	}
	return 0;
}

static void VS_SetZoom2D(VisualSurface *surf, Fixed zoom) 
{
	Fixed ratio;

	if (zoom <= 0) zoom = FIX_ONE/1000;
	if (zoom != surf->camera.zoom) {
		ratio = zoom/surf->camera.zoom;
		surf->camera.trans.x = m4_mulfix(surf->camera.trans.x, ratio);
		surf->camera.trans.y = m4_mulfix(surf->camera.trans.y, ratio);
		surf->camera.zoom = zoom;
	}
	camera_changed(surf->render, &surf->camera);
}

/*let's try to keep the same behaviour as the 2D renderer*/
static Bool VS_HandleEvents2D(VisualSurface *surf, M4UserEvent *ev)
{
	Fixed x, y, dx, dy, key_trans, key_rot;
	s32 key_inv;
	Bool is_pixel_metrics = SG_UsePixelMetrics(surf->render->compositor->scene);
	u32 keys = surf->render->compositor->key_states;
	if (!surf->camera.navigate_mode) return 0;

	x = y = 0;
	/*renorm between -1, 1*/
	if (ev->event_type<=M4E_MOUSEWHEEL) {
		x = INT2FIX(ev->mouse.x);
		y = INT2FIX(ev->mouse.y);
	} else {
		if (!(keys & M4KM_ALT) && (!surf->camera.navigate_mode || !(surf->camera.navigation_flags & NAV_ANY)) ) return 0;
	}
	dx = x - surf->render->grab_x;
	dy = y - surf->render->grab_y;
	if (!is_pixel_metrics) { dx /= surf->width; dy /= surf->height; }

	key_inv = 1;
	key_trans = INT2FIX(2);
	key_rot = FIX_ONE/10;

	if (keys & M4KM_SHIFT) {
		dx *= 4;
		dy *= 4;
		key_rot *= 4;
		key_trans*=4;
	}

	if (!is_pixel_metrics) { key_trans /= surf->width;}

	switch (ev->event_type) {
	case M4E_LEFTDOWN:
		surf->render->grab_x = x;
		surf->render->grab_y = y;
		surf->render->nav_is_grabbed = 1;
		return 0;
	case M4E_LEFTUP:
		surf->render->nav_is_grabbed = 0;
		return 0;

	case M4E_MOUSEMOVE:
		if (!surf->render->nav_is_grabbed) return 0;
		switch (surf->camera.navigate_mode) {
		case M4_NavSlide:
			if (keys & M4KM_CTRL) {
				Fixed new_zoom = surf->camera.zoom;
				if (new_zoom > FIX_ONE) new_zoom += dy/20;
				else new_zoom += dy/80;
				VS_SetZoom2D(surf, new_zoom);
			} else {
				surf->camera.trans.x += dx;
				surf->camera.trans.y += dy;
				VS_SetZoom2D(surf, surf->camera.zoom);
			}
			break;
		case M4_NavExamine:
			surf->camera.rot.y += m4_mulfix(M4_PI, dx) / surf->width;
			surf->camera.rot.x += m4_mulfix(M4_PI, dy) / surf->height;
			camera_changed(surf->render, &surf->camera);
			break;
		}
		surf->render->grab_x = x;
		surf->render->grab_y = y;
		return 1;
	case M4E_VKEYDOWN:
		if (ev->key.virtual_code=='\b') SR_ResetGraphics(surf->render->compositor);
		switch (ev->key.m4_vk_code) {
		case M4VK_HOME:
			if (!surf->render->nav_is_grabbed) R3D_ResetCamera(surf->render);
			return 1;
		case M4VK_LEFT: key_inv = -1;
		case M4VK_RIGHT:
			if (surf->camera.navigate_mode == M4_NavSlide) surf->camera.trans.x += key_inv*key_trans;
			else surf->camera.rot.y -= key_inv * key_rot;
			camera_changed(surf->render, &surf->camera);
			return 1;
		case M4VK_DOWN: key_inv = -1;
		case M4VK_UP:
			if (surf->camera.navigate_mode == M4_NavSlide) {
				if (keys & M4KM_CTRL) {
					Fixed new_zoom = surf->camera.zoom;
					if (new_zoom > FIX_ONE) new_zoom += key_inv*FIX_ONE/10;
					else new_zoom += key_inv*FIX_ONE/20;
					VS_SetZoom2D(surf, new_zoom);
				} else {
					surf->camera.trans.y += key_inv*key_trans;
					camera_changed(surf->render, &surf->camera);
				}
			}
			else {
				surf->camera.rot.x += key_rot;
				camera_changed(surf->render, &surf->camera);
			}
			return 1;
		}
		break;
	}
	return 0;
}


Bool R3D_HandleUserEvent(Render3D *sr, M4UserEvent *ev)
{
	if (sr->root_is_3D || sr->active_layer) {
		return R3D_HandleEvents3D(sr, ev);
	} else {
		return VS_HandleEvents2D(sr->surface, ev);
	}
}

void VS_ViewpointChange(RenderEffect3D *eff, SFNode *vp, Bool animate_change, Fixed fieldOfView, SFVec3f position, SFRotation orientation, SFVec3f local_center)
{
	Fixed dist;
	SFVec3f d;

	/*update znear&zfar*/
	eff->camera->z_near = eff->camera->avatar_size.x / 20; 
	if (eff->camera->z_near<=0) eff->camera->z_near = FIX_ONE/100;
	eff->camera->z_far = eff->camera->visibility; 
	if (eff->camera->z_far<=0) {
		eff->camera->z_far = INT2FIX(1000);
		/*use the current graph pm settings, NOT the viewpoint one*/
		if (eff->is_pixel_metrics) {
#ifdef M4_FIXED_POINT
			/*avoid any overflow issue and pick a default large z-far*/
			eff->camera->z_far = FIX_MAX/10;
#else
			eff->camera->z_far = m4_mulfix(eff->camera->z_far , eff->min_hsize);
#endif
		}
	}

	if (vp) {
		/*now check if vp is in pixel metrics. If not then:
		- either it's in the main scene, there's nothing to do
		- or it's in an inline, and the inline has been scaled if main scene is in pm: nothing to do*/
		if (0 && SG_UsePixelMetrics(Node_GetParentGraph(vp))) {
			M4Matrix mx;
			m4_mx_init(mx);
			m4_mx_add_scale(&mx, eff->min_hsize, eff->min_hsize, eff->min_hsize);
			m4_mx_apply_vec(&mx, &position);
			m4_mx_apply_vec(&mx, &local_center);
		}
	}
	/*default VP setup - this is undocumented in the spec. Default VP pos is (0, 0, 10) but not really nice 
	in pixel metrics. We set z so that we see just the whole surface*/
	else if (eff->is_pixel_metrics) {
		position.z = m4_divfix(eff->camera->width, 2*m4_tan(fieldOfView/2) );
	}

	m4_vec_diff(d, position, local_center);
	dist = m4_vec_len(d);
	if (!dist || (dist<eff->camera->z_near) || (dist > eff->camera->z_far)) {
		if (dist > eff->camera->z_far) eff->camera->z_far = 2*dist;

		dist = 10 * eff->camera->avatar_size.x;
		if ((dist<eff->camera->z_near) || (dist > eff->camera->z_far)) 
			dist = (eff->camera->avatar_size.x + eff->camera->z_far) / 5;
	}
	eff->camera->vp_dist = dist;
	eff->camera->vp_position = position;
	eff->camera->vp_orientation = orientation;
	eff->camera->vp_fov = fieldOfView;
	eff->camera->examine_center = local_center;

	camera_reset_viewpoint(eff->camera, animate_change);
	SR_Invalidate(eff->surface->render->compositor, NULL);
}

void R3D_ResetCamera(Render3D *sr)
{
	M4Camera *cam;

	if (sr->active_layer) {
		cam = l3d_get_camera(sr->active_layer);
	} else {
		cam = &sr->surface->camera;
	}
	camera_reset_viewpoint(cam, 1);
	SR_Invalidate(sr->compositor, NULL);
}

