/*
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * This program 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
 * of the License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Contributor(s): Geoffrey Gollmer, Jorge Bernal
 *
 * ***** END GPL LICENSE BLOCK *****
 */

#include "KX_MouseActuator.h"
#include "KX_KetsjiEngine.h"
#include "SCA_MouseManager.h"
#include "SCA_IInputDevice.h"
#include "RAS_ICanvas.h"
#include "KX_GameObject.h"
#include "MT_Vector3.h"
#include "MT_Scalar.h"
#include "MT_assert.h"
#include "limits.h"

#include "BLI_math.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/* ------------------------------------------------------------------------- */
/* Native functions                                                          */
/* ------------------------------------------------------------------------- */

KX_MouseActuator::KX_MouseActuator(
	SCA_IObject* gameobj,

	KX_KetsjiEngine* ketsjiEngine,
	SCA_MouseManager* eventmgr,
	int acttype,
	bool visible,
	bool* use_axis,
	float* threshold,
	bool* reset,
	int* object_axis,
	bool* local,
	float* sensitivity,
	float* limit_x,
	float* limit_y
):
	SCA_IActuator(gameobj, KX_ACT_MOUSE),
	m_ketsji(ketsjiEngine),
	m_eventmgr(eventmgr),
	m_type(acttype),
	m_visible(visible),
	m_use_axis_x(use_axis[0]),
	m_use_axis_y(use_axis[1]),
	m_reset_x(reset[0]),
	m_reset_y(reset[1]),
	m_local_x(local[0]),
	m_local_y(local[1])
{
	m_canvas = m_ketsji->GetCanvas();
	m_oldposition[0] = m_oldposition[1] = -1.f;
	m_limit_x[0] = limit_x[0];
	m_limit_x[1] = limit_x[1];
	m_limit_y[0] = limit_y[0];
	m_limit_y[1] = limit_y[1];
	m_threshold[0] = threshold[0];
	m_threshold[1] = threshold[1];
	m_object_axis[0] = object_axis[0];
	m_object_axis[1] = object_axis[1];
	m_sensitivity[0] = sensitivity[0];
	m_sensitivity[1] = sensitivity[1];
	m_angle[0] = 0.f;
	m_angle[1] = 0.f;
}

KX_MouseActuator::~KX_MouseActuator()
{
}

bool KX_MouseActuator::Update()
{
	bool result = false;

	bool bNegativeEvent = IsNegativeEvent();
	RemoveAllEvents();

	if (bNegativeEvent)
		return false; // do nothing on negative events

	KX_GameObject *parent = static_cast<KX_GameObject *>(GetParent());

	m_mouse = ((SCA_MouseManager *)m_eventmgr)->GetInputDevice();

	switch (m_type) {
		case KX_ACT_MOUSE_VISIBILITY:
		{
			if (m_visible) {
				if (m_canvas) {
					m_canvas->SetMouseState(RAS_ICanvas::MOUSE_NORMAL);
				}
			}
			else {
				if (m_canvas) {
					m_canvas->SetMouseState(RAS_ICanvas::MOUSE_INVISIBLE);
				}
			}
			break;
		}
		case KX_ACT_MOUSE_LOOK:
		{
			if (m_mouse) {

				float position[2];
				float movement[2];
				MT_Vector3 rotation;
				float setposition[2] = {0.0};

				getMousePosition(position);

				movement[0] = position[0];
				movement[1] = position[1];

				//preventing initial skipping.
				if ((m_oldposition[0] <= -0.9) && (m_oldposition[1] <= -0.9)) {

					if (m_reset_x) {
						m_oldposition[0] = 0.5;
					}
					else {
						m_oldposition[0] = position[0];
					}

					if (m_reset_y) {
						m_oldposition[1] = 0.5;
					}
					else {
						m_oldposition[1] = position[1];
					}
					setMousePosition(m_oldposition[0], m_oldposition[1]);
					break;
				}

				//Calculating X axis.
				if (m_use_axis_x) {

					if (m_reset_x) {
						setposition[0] = 0.5;
						movement[0] -= 0.5;
					}
					else {
						setposition[0] = position[0];
						movement[0] -= m_oldposition[0];
					}

					movement[0] *= -1.0;

					/* Don't apply the rotation when width resolution is odd (+ little movement) to
					  avoid undesired drifting or when we are under a certain threshold for mouse
					  movement */

					if (!((m_canvas->GetWidth() % 2 != 0) && MT_abs(movement[0]) < 0.01) &&
					    ((movement[0] > (m_threshold[0] / 10.0)) ||
					    ((movement[0] * (-1.0)) > (m_threshold[0] / 10.0)))) {

						movement[0] *= m_sensitivity[0];

						if ((m_limit_x[0] != 0.0) && ((m_angle[0] + movement[0]) <= m_limit_x[0])) {
							movement[0] = m_limit_x[0] - m_angle[0];
						}

						if ((m_limit_x[1] != 0.0) && ((m_angle[0] + movement[0]) >= m_limit_x[1])) {
							movement[0] = m_limit_x[1] - m_angle[0];
						}

						m_angle[0] += movement[0];

						switch (m_object_axis[0]) {
							case KX_ACT_MOUSE_OBJECT_AXIS_X:
							{
								rotation = MT_Vector3(movement[0], 0.0, 0.0);
								break;
							}
							case KX_ACT_MOUSE_OBJECT_AXIS_Y:
							{
								rotation = MT_Vector3(0.0, movement[0], 0.0);
								break;
							}
							case KX_ACT_MOUSE_OBJECT_AXIS_Z:
							{
								rotation = MT_Vector3(0.0, 0.0, movement[0]);
								break;
							}
							default:
								break;
						}
						parent->ApplyRotation(rotation, m_local_x);
					}
				}
				else {
					setposition[0] = 0.5;
				}

				//Calculating Y axis.
				if (m_use_axis_y) {

					if (m_reset_y) {
						setposition[1] = 0.5;
						movement[1] -= 0.5;
					}
					else {
						setposition[1] = position[1];
						movement[1] -= m_oldposition[1];
					}

					movement[1] *= -1.0;

					/* Don't apply the rotation when height resolution is odd (+ little movement) to
					  avoid undesired drifting or when we are under a certain threshold for mouse
					  movement */

					if (!((m_canvas->GetHeight() % 2 != 0) && MT_abs(movement[1]) < 0.01) &&
					    ((movement[1] > (m_threshold[1] / 10.0)) ||
					    ((movement[1] * (-1.0)) > (m_threshold[1] / 10.0)))) {

						movement[1] *= m_sensitivity[1];

						if ((m_limit_y[0] != 0.0) && ((m_angle[1] + movement[1]) <= m_limit_y[0])) {
							movement[1] = m_limit_y[0] - m_angle[1];
						}

						if ((m_limit_y[1] != 0.0) && ((m_angle[1] + movement[1]) >= m_limit_y[1])) {
							movement[1] = m_limit_y[1] - m_angle[1];
						}

						m_angle[1] += movement[1];

						switch (m_object_axis[1])
						{
							case KX_ACT_MOUSE_OBJECT_AXIS_X:
							{
								rotation = MT_Vector3(movement[1], 0.0, 0.0);
								break;
							}
							case KX_ACT_MOUSE_OBJECT_AXIS_Y:
							{
								rotation = MT_Vector3(0.0, movement[1], 0.0);
								break;
							}
							case KX_ACT_MOUSE_OBJECT_AXIS_Z:
							{
								rotation = MT_Vector3(0.0, 0.0, movement[1]);
								break;
							}
							default:
								break;
						}
						parent->ApplyRotation(rotation, m_local_y);
					}
				}
				else {
					setposition[1] = 0.5;
				}

				setMousePosition(setposition[0], setposition[1]);

				m_oldposition[0] = position[0];
				m_oldposition[1] = position[1];

			}
			else {
				//printf("\nNo input device detected for mouse actuator\n");
			}
			break;
		}
		default:
			break;
		}
	return result;
}

bool KX_MouseActuator::isValid(KX_MouseActuator::KX_ACT_MOUSE_MODE mode)
{
	return ((mode > KX_ACT_MOUSE_NODEF) && (mode < KX_ACT_MOUSE_MAX));
}


CValue* KX_MouseActuator::GetReplica()
{
	KX_MouseActuator* replica = new KX_MouseActuator(*this);

	replica->ProcessReplica();
	return replica;
}

void KX_MouseActuator::ProcessReplica()
{
	SCA_IActuator::ProcessReplica();
}

void KX_MouseActuator::getMousePosition(float* pos)
{
	MT_assert(!m_mouse);
	const SCA_InputEvent & xevent = m_mouse->GetEventValue(SCA_IInputDevice::KX_MOUSEX);
	const SCA_InputEvent & yevent = m_mouse->GetEventValue(SCA_IInputDevice::KX_MOUSEY);

	pos[0] = m_canvas->GetMouseNormalizedX(xevent.m_eventval);
	pos[1] = m_canvas->GetMouseNormalizedY(yevent.m_eventval);
}

void KX_MouseActuator::setMousePosition(float fx, float fy)
{
	int x, y;

	x = (int)(fx * m_canvas->GetWidth());
	y = (int)(fy * m_canvas->GetHeight());

	m_canvas->SetMousePosition(x, y);
}

#ifndef DISABLE_PYTHON

/* ------------------------------------------------------------------------- */
/* Python functions                                                          */
/* ------------------------------------------------------------------------- */

/* Integration hooks ------------------------------------------------------- */
PyTypeObject KX_MouseActuator::Type = {
	PyVarObject_HEAD_INIT(NULL, 0)
	"KX_MouseActuator",
	sizeof(PyObjectPlus_Proxy),
	0,
	py_base_dealloc,
	0,
	0,
	0,
	0,
	py_base_repr,
	0,0,0,0,0,0,0,0,0,
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
	0,0,0,0,0,0,0,
	Methods,
	0,
	0,
	&SCA_IActuator::Type,
	0,0,0,0,0,0,
	py_base_new
};

PyMethodDef KX_MouseActuator::Methods[] = {
	{"reset", (PyCFunction) KX_MouseActuator::sPyReset, METH_NOARGS,"reset() : undo rotation caused by actuator\n"},
	{NULL,NULL} //Sentinel
};



PyAttributeDef KX_MouseActuator::Attributes[] = {
	KX_PYATTRIBUTE_BOOL_RW("visible", KX_MouseActuator, m_visible),
	KX_PYATTRIBUTE_BOOL_RW("use_axis_x", KX_MouseActuator, m_use_axis_x),
	KX_PYATTRIBUTE_BOOL_RW("use_axis_y", KX_MouseActuator, m_use_axis_y),
	KX_PYATTRIBUTE_FLOAT_ARRAY_RW("threshold", 0.0, 0.5, KX_MouseActuator, m_threshold, 2),
	KX_PYATTRIBUTE_BOOL_RW("reset_x", KX_MouseActuator, m_reset_x),
	KX_PYATTRIBUTE_BOOL_RW("reset_y", KX_MouseActuator, m_reset_y),
	KX_PYATTRIBUTE_INT_ARRAY_RW("object_axis", 0, 2, 1, KX_MouseActuator, m_object_axis, 2),
	KX_PYATTRIBUTE_BOOL_RW("local_x", KX_MouseActuator, m_local_x),
	KX_PYATTRIBUTE_BOOL_RW("local_y", KX_MouseActuator, m_local_y),
	KX_PYATTRIBUTE_FLOAT_ARRAY_RW("sensitivity", -FLT_MAX, FLT_MAX, KX_MouseActuator, m_sensitivity, 2),
	KX_PYATTRIBUTE_RW_FUNCTION("limit_x", KX_MouseActuator, pyattr_get_limit_x, pyattr_set_limit_x),
	KX_PYATTRIBUTE_RW_FUNCTION("limit_y", KX_MouseActuator, pyattr_get_limit_y, pyattr_set_limit_y),
	KX_PYATTRIBUTE_RW_FUNCTION("angle", KX_MouseActuator, pyattr_get_angle, pyattr_set_angle),
	{ NULL }	//Sentinel
};

PyObject* KX_MouseActuator::pyattr_get_limit_x(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
{
	KX_MouseActuator* self= static_cast<KX_MouseActuator*>(self_v);
	return Py_BuildValue("[f,f]", (self->m_limit_x[0] / M_PI * 180.0), (self->m_limit_x[1] / M_PI * 180.0));
}

int KX_MouseActuator::pyattr_set_limit_x(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef, PyObject *value)
{
	PyObject *item1, *item2;
	KX_MouseActuator* self= static_cast<KX_MouseActuator*>(self_v);

	if (!PyList_Check(value))
		return PY_SET_ATTR_FAIL;

	if (PyList_Size(value) != 2)
		return PY_SET_ATTR_FAIL;

	item1 = PyList_GetItem(value, 0);
	item2 = PyList_GetItem(value, 1);

	if (!(PyFloat_Check(item1)) || !(PyFloat_Check(item2))) {
		return PY_SET_ATTR_FAIL;
	}
	else {
		self->m_limit_x[0] = (PyFloat_AsDouble(item1) * M_PI / 180.0);
		self->m_limit_x[1] = (PyFloat_AsDouble(item2) * M_PI / 180.0);
	}

	return PY_SET_ATTR_SUCCESS;
}

PyObject* KX_MouseActuator::pyattr_get_limit_y(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
{
	KX_MouseActuator* self= static_cast<KX_MouseActuator*>(self_v);
	return Py_BuildValue("[f,f]", (self->m_limit_y[0] / M_PI * 180.0), (self->m_limit_y[1] / M_PI * 180.0));
}

int KX_MouseActuator::pyattr_set_limit_y(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef, PyObject *value)
{
	PyObject *item1, *item2;
	KX_MouseActuator* self= static_cast<KX_MouseActuator*>(self_v);

	if (!PyList_Check(value))
		return PY_SET_ATTR_FAIL;

	if (PyList_Size(value) != 2)
		return PY_SET_ATTR_FAIL;

	item1 = PyList_GetItem(value, 0);
	item2 = PyList_GetItem(value, 1);

	if (!(PyFloat_Check(item1)) || !(PyFloat_Check(item2))) {
		return PY_SET_ATTR_FAIL;
	}
	else {
		self->m_limit_y[0] = (PyFloat_AsDouble(item1) * M_PI / 180.0);
		self->m_limit_y[1] = (PyFloat_AsDouble(item2) * M_PI / 180.0);
	}

	return PY_SET_ATTR_SUCCESS;
}

PyObject* KX_MouseActuator::pyattr_get_angle(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
{
	KX_MouseActuator* self= static_cast<KX_MouseActuator*>(self_v);
	return Py_BuildValue("[f,f]", (self->m_angle[0] / M_PI * 180.0), (self->m_angle[1] / M_PI * 180.0));
}

int KX_MouseActuator::pyattr_set_angle(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef, PyObject *value)
{
	PyObject *item1, *item2;
	KX_MouseActuator* self= static_cast<KX_MouseActuator*>(self_v);

	if (!PyList_Check(value))
		return PY_SET_ATTR_FAIL;

	if (PyList_Size(value) != 2)
		return PY_SET_ATTR_FAIL;

	item1 = PyList_GetItem(value, 0);
	item2 = PyList_GetItem(value, 1);

	if (!(PyFloat_Check(item1)) || !(PyFloat_Check(item2))) {
		return PY_SET_ATTR_FAIL;
	}
	else {
		self->m_angle[0] = (PyFloat_AsDouble(item1) * M_PI / 180.0);
		self->m_angle[1] = (PyFloat_AsDouble(item2) * M_PI / 180.0);
	}

	return PY_SET_ATTR_SUCCESS;
}

PyObject* KX_MouseActuator::PyReset()
{
	MT_Vector3 rotation;
	KX_GameObject *parent = static_cast<KX_GameObject *>(GetParent());

	switch (m_object_axis[0]) {
		case KX_ACT_MOUSE_OBJECT_AXIS_X:
		{
			rotation = MT_Vector3(-1.0 * m_angle[0], 0.0, 0.0);
			break;
		}
		case KX_ACT_MOUSE_OBJECT_AXIS_Y:
		{
			rotation = MT_Vector3(0.0, -1.0 * m_angle[0], 0.0);
			break;
		}
		case KX_ACT_MOUSE_OBJECT_AXIS_Z:
		{
			rotation = MT_Vector3(0.0, 0.0, -1.0 * m_angle[0]);
			break;
		}
		default:
			break;
	}
	parent->ApplyRotation(rotation, m_local_x);

	switch (m_object_axis[1]) {
		case KX_ACT_MOUSE_OBJECT_AXIS_X:
		{
			rotation = MT_Vector3(-1.0 * m_angle[1], 0.0, 0.0);
			break;
		}
		case KX_ACT_MOUSE_OBJECT_AXIS_Y:
		{
			rotation = MT_Vector3(0.0, -1.0 * m_angle[1], 0.0);
			break;
		}
		case KX_ACT_MOUSE_OBJECT_AXIS_Z:
		{
			rotation = MT_Vector3(0.0, 0.0, -1.0 * m_angle[1]);
			break;
		}
		default:
			break;
	}
	parent->ApplyRotation(rotation, m_local_y);

	m_angle[0] = 0.0;
	m_angle[1] = 0.0;

	Py_RETURN_NONE;
}

#endif
