/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "binding_helpers.h"
		#include "scene3d/scene.h"
		#include "scene3d/mobject.h"
		#include "scene3d/mcamera.h"
		#include "script_squirrel/cobject/matrix_decl.h"
		#include "core/renderer.h"
		#include "math/vector.h"

		using namespace GS;
		using namespace GS::S3D;
		using namespace GS::Script;


static CObjectType camera_derived_types[] = { typetag_Item, typetag_Camera, typetag_Undefined };
static CObjectType object_derived_types[] = { typetag_Item, typetag_Object, typetag_Undefined };

//------------------------------------------------------------------------------
SQInteger			CameraGetItem(HSQUIRRELVM vm)
{
	__SQ_GETSINGLESAFEPTR(cam, MCamera, typetag_Camera)
	__SQ_RETURNSAFEPTR((MItem *)cam, typetag_Item)
}
SQInteger			CameraGetFov(HSQUIRRELVM vm)
{
	__SQ_GETSINGLE(__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types))
	__SQ_RETURNFLOAT(c->GetFov())
}
SQInteger			CameraSetFov(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
	__SQ_GETFLOAT(f)
	__SQ_GETEND
	c->SetFov(f);
	__SQ_RETURN
}
SQInteger			CameraGetZoomFactor(HSQUIRRELVM vm)
{
	__SQ_GETSINGLE(__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types))
		__SQ_RETURNFLOAT(c->zoom_factor)
}
SQInteger			CameraSetZoomFactor(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
		__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
		__SQ_GETFLOAT(z)
		__SQ_GETEND
		c->SetZoomFactor(z);
	__SQ_RETURN
}
SQInteger			CameraSetFStop(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
	__SQ_GETFLOAT(f)
	__SQ_GETEND
	c->registry.CreateKey("PostProcess:Dof:FStop", f);
	__SQ_RETURN
}
SQInteger			CameraSetFocalDistance(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
	__SQ_GETFLOAT(f)
	__SQ_GETEND
	c->registry.CreateKey("PostProcess:Dof:FDist", f);
	__SQ_RETURN
}
SQInteger CameraGetVisibleItems(HSQUIRRELVM vm) {
    __SQ_GETSTART(2)
    __SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
    __SQ_GETSAFEPTR(scene, Scene, typetag_Scene3d)
    __SQ_GETEND

    sq_newarray(vm, 0);
    Vector4 cameraPos = c->GetMatrix().GetRow(3);
    Vector4 cameraForward = c->GetMatrix().GetRow(2); // Camera's forward vector

    ListForeachPtr(MItem *, item, scene->GetItemList()) {
        if (item->GetItemType() == Type_Object) {
            if (MObject *o = (MObject *)item) {
                if (o->render_data.IsValid() && o->render_data->geometry.IsValid()) {
                    Vector4 objectPos = o->GetMatrix().GetRow(3);
                    Vector4 toObject = objectPos - cameraPos;

					// Skip this object if it's too far
					float distance = toObject.Len();
					if (distance > 500) {
						continue;
					}

                    // Check if object is in front of the camera
                    float dot = toObject.x * cameraForward.x + toObject.y * cameraForward.y + toObject.z * cameraForward.z;
                    if (dot > 0) {
                        // Perform frustum culling
                        Frustum::Visibility vis = c->frustum.ClassifyMinMax(o->render_data->geometry->minmax, &o->GetMatrix());
                        if (vis != Frustum::Outside) {
                            CObject::Push(vm, (void *)item, typetag_Item);
                            sq_arrayappend(vm, -2);
                        }
                    }
                }
            }
        }
    }
    return 1;
}
/*
SQInteger			CameraComputeProjectionMatrix(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
	__SQ_GETFRECT(viewport)
	__SQ_GETEND
	Matrix4 projection_matrix;
	c->ComputeProjectionMatrix(viewport, projection_matrix);
	__SQ_RETURNMATRIX4(projection_matrix)
}
*/
SQInteger			CameraSetAspectRatio(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
	__SQ_GETFLOAT(v)
	__SQ_GETEND
	c->aspect_ratio = v;
	__SQ_RETURN
}
SQInteger			CameraGetAspectRatio(HSQUIRRELVM vm)
{
	__SQ_GETSINGLE(__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types))
	__SQ_RETURNFLOAT(c->aspect_ratio)
}
SQInteger			CameraSetAspectRatioRefAxis(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
	__SQ_GETBOOL(v)
	__SQ_GETEND
	c->aspect_ratio_ref_yaxis = asbool(v);
	__SQ_RETURN
}
SQInteger			CameraSetClipping(HSQUIRRELVM vm)
{
	__SQ_GETSTART(3)
	__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
	__SQ_GETFLOAT(n)
	__SQ_GETFLOAT(f)
	__SQ_GETEND
	c->z_near = n;
	c->z_far = f;
	__SQ_RETURN
}

SQInteger			CameraGetZNear(HSQUIRRELVM vm)
{
	__SQ_GETSTART(1)
	__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
	__SQ_GETEND
	__SQ_RETURNFLOAT(c->z_near)
}

SQInteger			CameraGetZFar(HSQUIRRELVM vm)
{
	__SQ_GETSTART(1)
	__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
	__SQ_GETEND
	__SQ_RETURNFLOAT(c->z_far)
}


SQInteger			CameraCullObject(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
	__SQ_GETCOBJECTBASE(o, MObject, object_derived_types)
	__SQ_GETEND

	Frustum::Visibility vis = Frustum::Outside;
	if	(o->render_data.IsValid() && o->render_data->geometry.IsValid())
		vis = c->frustum.ClassifyMinMax(o->render_data->geometry->minmax, &o->GetMatrix());

	__SQ_RETURNINT(int(vis))
}
SQInteger			CameraCullPosition(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
	__SQ_GETVECTOR(p)
	__SQ_GETEND
	__SQ_RETURNINT(int(c->frustum.ClassifySet(1, &p)))
}

SQInteger CameraGetFrustumVertices(HSQUIRRELVM vm)
{
    __SQ_GETSTART(1)
    __SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
    __SQ_GETEND

    const Vector4* vtx = c->frustum.GetVertices();

    sq_newarray(vm, 0);

    for (int i = 0; i < 8; ++i)
    {
        PushVector(vm, vtx[i], false);
        sq_arrayappend(vm, -2);
    }

    return 1;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
SQInteger			CameraWorldToScreen(HSQUIRRELVM vm)
{
	__SQ_GETSTART(3)
	__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
	__SQ_GETSAFEPTR(renderer, Renderer, typetag_Renderer)
	__SQ_GETVECTOR(world)
	__SQ_GETEND
	Vector4 screen;
	if	(!c->WorldToScreen(renderer->GetViewport(), world, screen))
		screen.Set(-1, -1, -1, -1);
	__SQ_RETURNVECTOR(screen)
}
SQInteger			CameraScreenToWorld(HSQUIRRELVM vm)
{
	__SQ_GETSTART(4)
	__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
	__SQ_GETSAFEPTR(renderer, Renderer, typetag_Renderer)
	__SQ_GETFLOAT(x)
	__SQ_GETFLOAT(y)
	__SQ_GETEND
	__SQ_RETURNVECTOR(c->ScreenToWorld(renderer->GetViewport(), x, y))
}
SQInteger			CameraScreenToWorldPlane(HSQUIRRELVM vm)
{
	__SQ_GETSTART(5)
	__SQ_GETCOBJECTBASE(c, MCamera, camera_derived_types)
	__SQ_GETSAFEPTR(renderer, Renderer, typetag_Renderer)
	__SQ_GETFLOAT(x)
	__SQ_GETFLOAT(y)
	__SQ_GETFLOAT(z)
	__SQ_GETEND
	__SQ_RETURNVECTOR(c->ScreenToWorld(renderer->GetViewport(), x, y, z))
}
//------------------------------------------------------------------------------

//-------------------------------------------------------
void				RegisterCameraBinding(HSQUIRRELVM vm)
//-------------------------------------------------------
{
/*#
	Topic: Camera
	Type: Camera
	Related: Item
#*/

/*#
	Section: CameraGeneric
	Desc: Generic functions
#*/
	/*#
		Func: CameraGetItem
		Proto: Item:Camera
		Example: local camera_item = CameraGetItem(camera)
		Desc: Return camera item.
	#*/
	sq_register(vm, CameraGetItem, "CameraGetItem", _SC(".x"));

/*#
	Section: CameraAR
	Desc: Aspect ratio functions.
#*/
	/*#
		Func: CameraSetAspectRatio
		Proto: void:Camera,float aspect_ratio
		Desc: Set camera aspect ratio.
	#*/
	sq_register(vm, CameraSetAspectRatio, "CameraSetAspectRatio", _SC(".xn"));
	/*#
		Func: CameraGetAspectRatio
		Proto: float:Camera
		Desc: Get camera aspect ratio.
	#*/
	sq_register(vm, CameraGetAspectRatio, "CameraGetAspectRatio", _SC(".x"));
	/*#
		Func: CameraSetAspectRatioRefAxis
		Proto: void:Camera,bool use_Y_axis
		Desc: Set camera aspect ratio correction to be done on the vertical screen axis (Y) instead of the horizontal axis (X).
	#*/
	sq_register(vm, CameraSetAspectRatioRefAxis, "CameraSetAspectRatioRefAxis", _SC(".xb"));

/*#
	Section: CameraViewport
	Desc: Viewport functions
#*/
	/*#
		Func: CameraCullObject
		Proto: VisibilityFlags:Camera camera_to_cull_against,Object object_to_cull
		Desc: Cull an object against the camera frustum. Returns a visibility flag mask.
	#*/
	sq_register(vm, CameraCullObject, "CameraCullObject", _SC(".xx"));
	/*#
		Func: CameraCullPosition
		Proto: VisibilityFlags:Camera camera_to_cull_against,Vector world_position
		Desc: Cull a position in world space against the camera frustum. Returns a visibility flag mask.
	#*/
	sq_register(vm, CameraCullPosition, "CameraCullPosition", _SC(".xx"));

	sq_register(vm, CameraGetFrustumVertices, "CameraGetFrustumVertices", _SC(".x"));
	sq_register(vm, CameraGetZNear, "CameraGetZNear", _SC(".x"));
	sq_register(vm, CameraGetZFar, "CameraGetZFar", _SC(".x"));

	/*#
		Func: CameraGetFov
		Proto: float:Camera
		Desc: Get camera fov in radian.
	#*/
	sq_register(vm, CameraGetFov, "CameraGetFov", _SC(".x"));
	/*#
		Func: CameraSetFov
		Proto: void:Camera,float fov_in_radian
		Example: CameraSetFov(camera, Deg(60))
		Desc: Get camera fov in radian.
	#*/
	sq_register(vm, CameraSetFov, "CameraSetFov", _SC(".xn"));

	/*#
		Func: CameraGetZoomFactor
		Proto: float:Camera
		Desc: Get camera zoom.
	#*/
	sq_register(vm, CameraGetZoomFactor, "CameraGetZoomFactor", _SC(".x"));
	/*#
		Func: CameraSetZoomFactor
		Proto: void:Camera,float zoom_factor
		Example: CameraSetZoomFactor(camera, 3.2)
		Desc: Set camera zoom.
	#*/
	sq_register(vm, CameraSetZoomFactor, "CameraSetZoomFactor", _SC(".xn"));


	/*#
		Func: CameraSetFocalDistance
		Proto: void:Camera,float distance_in_meters
		Example:
// Set focus 10 meters from camera. Objects closer or father than 10 meters will appear out of focus.
CameraSetFocalDistance(camera, Mtr(10))
		Desc: Set camera focal distance, set to 0 or less to disable depth of field on the camera.
	#*/
	sq_register(vm, CameraSetFocalDistance, "CameraSetFocalDistance", _SC(".xn"));
	/*#
		Func: CameraSetFStop
		Proto: void:Camera,float fstop_in_meters
		Example: CameraSetFStop(camera, Mtr(4))
		Desc: Set camera f-stop, set to 0 or less to disable depth of field on the camera.
	#*/
	sq_register(vm, CameraSetFStop, "CameraSetFStop", _SC(".xn"));
	/*#
		Func: CameraSetClipping
		Proto: void:camera,float near, float far
		Example: CameraSetClipping(camera, Cm(1), Mtr(100))
		Desc: Set camera near and far clipping planes.
	#*/
	sq_register(vm, CameraSetClipping, "CameraSetClipping", _SC(".xnn"));
	/*#
		Func: CameraWorldToScreen
		Proto: vector:Camera,Renderer,Vector world_position
		Example:
// Project world position {10,5,1} on the screen.
local p2d = CameraWorldToScreen(camera, g_render, Vector(10, 5, 1))
		Desc: Transform a world space position to a normalized screen space position, in the range { [0;1], [0;1] }.
	#*/
	sq_register(vm, CameraWorldToScreen, "CameraWorldToScreen", _SC(".xxx"));
	/*#
		Func: CameraScreenToWorld
		Proto: vector:Camera,Renderer,float x,float y
		Example:
// Transform the middle of the screen to a 3d world position 1 meter away from the camera.
local wp = CameraScreenToWorld(camera, g_render, 0.5, 0.5)
		Desc: Transform a normalized screen space position to a world space position.<br>
		The screen space position is the result of a projection on the Z=1 imaginary plane, situated 1 meter in front of the camera.
		See: CameraScreenToWorldPlane
	#*/
	sq_register(vm, CameraScreenToWorld, "CameraScreenToWorld", _SC(".xxnn"));
	/*#
		Func: CameraScreenToWorldPlane
		Proto: vector:Camera,Renderer,float x,float y,float plane_distance_in_meters
		Desc: Transform a normalized screen space position to a world space position.<br>
		The screen space position is the result of a projection on an imaginary plane situated in front of the camera.
	#*/
	sq_register(vm, CameraScreenToWorldPlane, "CameraScreenToWorldPlane", _SC(".xxnnn"));

	sq_pushroottable(vm);

	/*#
		Enum: VisibilityFlags
		Desc: Reported by the visibility functions, visibility can be total, partial or null.
		Values: VisibilityOutside,VisibilityClipped,VisibilityInside
	#*/
	sq_pushstring(vm, "VisibilityOutside", -1); sq_pushinteger(vm, Frustum::Outside); sq_newslot(vm, -3, true);
	sq_pushstring(vm, "VisibilityClipped", -1); sq_pushinteger(vm, Frustum::Clipped); sq_newslot(vm, -3, true);
	sq_pushstring(vm, "VisibilityInside", -1); sq_pushinteger(vm, Frustum::Inside); sq_newslot(vm, -3, true);
		/*#
		Func: CameraGetVisibleItems
		Proto: array:Camera,Scene3d
		Desc: Get all visible items in the camera frustum.
	#*/
	sq_register(vm, CameraGetVisibleItems, "CameraGetVisibleItems", _SC(".xx"));


	sq_pop(vm, 1);
}
