/*
 * ***** 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.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 *
 * The Original Code is: all of this file.
 *
 * Contributor(s): none yet.
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file gameengine/Converter/KX_ConvertSensors.cpp
 *  \ingroup bgeconv
 *
 * Conversion of Blender data blocks to KX sensor system
 */

#include <stdio.h>

#ifdef _MSC_VER
#  pragma warning (disable:4786)
#endif

#include "wm_event_types.h"
#include "KX_BlenderSceneConverter.h"
#include "KX_ConvertSensors.h"

/* This little block needed for linking to Blender... */
#ifdef _MSC_VER
#  include "BLI_winstuff.h"
#endif

#include "DNA_object_types.h"
#include "DNA_material_types.h"
#include "DNA_sensor_types.h"
#include "DNA_controller_types.h"
#include "DNA_actuator_types.h" /* for SENS_ALL_KEYS ? this define is
                                 * probably misplaced */
/* end of blender include block */

#include "RAS_IPolygonMaterial.h"
// Sensors
#include "KX_GameObject.h"
#include "RAS_MeshObject.h"
#include "SCA_KeyboardSensor.h"
#include "SCA_MouseSensor.h"
#include "SCA_AlwaysSensor.h"
#include "KX_TouchSensor.h"
#include "KX_NearSensor.h"
#include "KX_RadarSensor.h"
#include "KX_MouseFocusSensor.h"
#include "KX_ArmatureSensor.h"
#include "SCA_JoystickSensor.h"
#include "KX_NetworkMessageSensor.h"
#include "SCA_ActuatorSensor.h"
#include "SCA_DelaySensor.h"


#include "SCA_PropertySensor.h"
#include "SCA_RandomSensor.h"
#include "KX_RaySensor.h"
#include "SCA_EventManager.h"
#include "SCA_LogicManager.h"
#include "KX_BlenderInputDevice.h"
#include "KX_Scene.h"
#include "EXP_IntValue.h"
#include "KX_BlenderKeyboardDevice.h"
#include "RAS_ICanvas.h"
#include "PHY_IPhysicsEnvironment.h"

#include "KX_KetsjiEngine.h"
#include "BL_BlenderDataConversion.h"

void BL_ConvertSensors(struct Object* blenderobject,
					   class KX_GameObject* gameobj,
					   SCA_LogicManager* logicmgr,
					   KX_Scene* kxscene,
					   KX_KetsjiEngine* kxengine,
					   int activeLayerBitInfo,
					   bool isInActiveLayer,
					   RAS_ICanvas* canvas,
					   KX_BlenderSceneConverter* converter
					   )
{

	int executePriority = 0;
	int uniqueint = 0;
	int count = 0;
	bSensor* sens = (bSensor*)blenderobject->sensors.first;
	bool pos_pulsemode = false;
	bool neg_pulsemode = false;
	int skipped_ticks = 0;
	bool invert = false;
	bool level = false;
	bool tap = false;
	
	while (sens)
	{
		sens = sens->next;
		count++;
	}
	gameobj->ReserveSensor(count);
	sens = (bSensor*)blenderobject->sensors.first;

	while (sens) {
		if (!(sens->flag & SENS_DEACTIVATE)) {
			SCA_ISensor* gamesensor=NULL;
			/* All sensors have a pulse toggle, skipped ticks parameter, and invert field.     */
			/* These are extracted here, and set when the sensor is added to the */
			/* list.                                                             */
			pos_pulsemode = (sens->pulse & SENS_PULSE_REPEAT)!=0;
			neg_pulsemode = (sens->pulse & SENS_NEG_PULSE_MODE)!=0;

			skipped_ticks = sens->freq;
			invert    = !(sens->invert == 0);
			level     = !(sens->level == 0);
			tap       = !(sens->tap == 0);

			switch (sens->type)
			{
			case  SENS_ALWAYS:
				{

					SCA_EventManager* eventmgr = logicmgr->FindEventManager(SCA_EventManager::BASIC_EVENTMGR);
					if (eventmgr)
					{
						gamesensor = new SCA_AlwaysSensor(eventmgr, gameobj);
					}

					break;
				}

			case  SENS_DELAY:
				{
					// we can reuse the Always event manager for the delay sensor
					SCA_EventManager* eventmgr = logicmgr->FindEventManager(SCA_EventManager::BASIC_EVENTMGR);
					if (eventmgr)
					{
						bDelaySensor* delaysensor = (bDelaySensor*)sens->data;
						gamesensor = new SCA_DelaySensor(eventmgr,
							gameobj,
							delaysensor->delay,
							delaysensor->duration,
							(delaysensor->flag & SENS_DELAY_REPEAT) != 0);
					}
					break;
				}

			case SENS_COLLISION:
				{
					SCA_EventManager* eventmgr = logicmgr->FindEventManager(SCA_EventManager::TOUCH_EVENTMGR);
					if (eventmgr)
					{
						// collision sensor can sense both materials and properties.

						bool bFindMaterial = false, bTouchPulse = false;

						bCollisionSensor* blendertouchsensor = (bCollisionSensor*)sens->data;

						bFindMaterial = (blendertouchsensor->mode & SENS_COLLISION_MATERIAL);
						bTouchPulse = (blendertouchsensor->mode & SENS_COLLISION_PULSE);


						const STR_String touchPropOrMatName = bFindMaterial ?
															  blendertouchsensor->materialName : blendertouchsensor->name;


						if (gameobj->GetPhysicsController())
						{
							gamesensor = new KX_TouchSensor(eventmgr,
								gameobj,
								bFindMaterial,
								bTouchPulse,
								touchPropOrMatName);
						}

					}

					break;
				}
			case SENS_MESSAGE:
				{
					KX_NetworkEventManager* eventmgr = (KX_NetworkEventManager*)
						logicmgr->FindEventManager(SCA_EventManager::NETWORK_EVENTMGR);
					if (eventmgr) {
						bMessageSensor* msgSens = (bMessageSensor*) sens->data;

						/* Get our NetworkScene */
						NG_NetworkScene *NetworkScene = kxscene->GetNetworkScene();
						/* filter on the incoming subjects, might be empty */
						const STR_String subject = msgSens->subject;

						gamesensor = new KX_NetworkMessageSensor(
							eventmgr,		// our eventmanager
							NetworkScene,	// our NetworkScene
							gameobj,		// the sensor controlling object
							subject);		// subject to filter on
					}
					break;
				}
			case SENS_NEAR:
				{

					SCA_EventManager* eventmgr = logicmgr->FindEventManager(SCA_EventManager::TOUCH_EVENTMGR);
					if (eventmgr)
					{
						bNearSensor* blendernearsensor = (bNearSensor*)sens->data;
						const STR_String nearpropertyname = (char *)blendernearsensor->name;

						//DT_ShapeHandle shape	=	DT_Sphere(0.0);

						// this sumoObject is not deleted by a gameobj, so delete it ourself
						// later (memleaks)!
						float radius = blendernearsensor->dist;
						const MT_Vector3& wpos = gameobj->NodeGetWorldPosition();
						bool bFindMaterial = false;
						PHY_IPhysicsController* physCtrl = kxscene->GetPhysicsEnvironment()->CreateSphereController(radius,wpos);

						//will be done in KX_TouchEventManager::RegisterSensor()
						//if (isInActiveLayer)
						//	kxscene->GetPhysicsEnvironment()->addSensor(physCtrl);



						gamesensor = new KX_NearSensor(eventmgr,gameobj,
							blendernearsensor->dist,
							blendernearsensor->resetdist,
							bFindMaterial,
							nearpropertyname,
							physCtrl);

					}
					break;
				}


			case SENS_KEYBOARD:
				{
					/* temporary input device, for converting the code for the keyboard sensor */

					bKeyboardSensor* blenderkeybdsensor = (bKeyboardSensor*)sens->data;
					SCA_KeyboardManager* eventmgr = (SCA_KeyboardManager*) logicmgr->FindEventManager(SCA_EventManager::KEYBOARD_EVENTMGR);
					if (eventmgr)
					{
						gamesensor = new SCA_KeyboardSensor(eventmgr,
							ConvertKeyCode(blenderkeybdsensor->key),
							ConvertKeyCode(blenderkeybdsensor->qual),
							ConvertKeyCode(blenderkeybdsensor->qual2),
							(blenderkeybdsensor->type == SENS_ALL_KEYS),
							blenderkeybdsensor->targetName,
							blenderkeybdsensor->toggleName,
							gameobj,
							KX_KetsjiEngine::GetExitKey()); //			blenderkeybdsensor->pad);

					}

					break;
				}
			case SENS_MOUSE:
				{
					int keytype = SCA_MouseSensor::KX_MOUSESENSORMODE_NODEF;
					int trackfocus = 0;
					bMouseSensor *bmouse = (bMouseSensor *)sens->data;

					/* There are two main types of mouse sensors. If there is
					 * no focus-related behavior requested, we can make do
					 * with a basic sensor. This cuts down memory usage and
					 * gives a slight performance gain. */

					SCA_MouseManager *eventmgr
						= (SCA_MouseManager*) logicmgr->FindEventManager(SCA_EventManager::MOUSE_EVENTMGR);
					if (eventmgr) {

						/* Determine key mode. There is at most one active mode. */
						switch (bmouse->type) {
						case BL_SENS_MOUSE_LEFT_BUTTON:
							keytype = SCA_MouseSensor::KX_MOUSESENSORMODE_LEFTBUTTON;
							break;
						case BL_SENS_MOUSE_MIDDLE_BUTTON:
							keytype = SCA_MouseSensor::KX_MOUSESENSORMODE_MIDDLEBUTTON;
							break;
						case BL_SENS_MOUSE_RIGHT_BUTTON:
							keytype = SCA_MouseSensor::KX_MOUSESENSORMODE_RIGHTBUTTON;
							break;
						case BL_SENS_MOUSE_WHEEL_UP:
							keytype = SCA_MouseSensor::KX_MOUSESENSORMODE_WHEELUP;
							break;
						case BL_SENS_MOUSE_WHEEL_DOWN:
							keytype = SCA_MouseSensor::KX_MOUSESENSORMODE_WHEELDOWN;
							break;
						case BL_SENS_MOUSE_MOVEMENT:
							keytype = SCA_MouseSensor::KX_MOUSESENSORMODE_MOVEMENT;
							break;
						case BL_SENS_MOUSE_MOUSEOVER:
							trackfocus = 1;
							break;
						case BL_SENS_MOUSE_MOUSEOVER_ANY:
							trackfocus = 2;
							break;

						default:
							; /* error */
						}

						/* initial mouse position */
						int startx  = canvas->GetWidth()/2;
						int starty = canvas->GetHeight()/2;

						if (!trackfocus) {
							/* plain, simple mouse sensor */
							gamesensor = new SCA_MouseSensor(eventmgr,
								startx,starty,
								keytype,
								gameobj);
						} else {
							/* give us a focus-aware sensor */
							bool bFindMaterial = (bmouse->mode & SENS_COLLISION_MATERIAL);
							bool bXRay = (bmouse->flag & SENS_RAY_XRAY);
							STR_String checkname = (bFindMaterial? bmouse->matname : bmouse->propname);

							gamesensor = new KX_MouseFocusSensor(eventmgr,
								startx,
								starty,
								keytype,
								trackfocus,
								(bmouse->flag & SENS_MOUSE_FOCUS_PULSE) ? true:false,
								checkname,
								bFindMaterial,
								bXRay,
								kxscene,
								kxengine,
								gameobj);
						}
					} else {
						//				cout << "\n Could't find mouse event manager..."; - should throw an error here...
					}
					break;
				}
			case SENS_PROPERTY:
				{
					bPropertySensor* blenderpropsensor = (bPropertySensor*) sens->data;
					SCA_EventManager* eventmgr
						= logicmgr->FindEventManager(SCA_EventManager::BASIC_EVENTMGR);
					if (eventmgr)
					{
						STR_String propname=blenderpropsensor->name;
						STR_String propval=blenderpropsensor->value;
						STR_String propmaxval=blenderpropsensor->maxvalue;

						SCA_PropertySensor::KX_PROPSENSOR_TYPE
							propchecktype = SCA_PropertySensor::KX_PROPSENSOR_NODEF;

						/* Better do an explicit conversion here! (was implicit      */
						/* before...)                                                */
						switch (blenderpropsensor->type) {
						case SENS_PROP_EQUAL:
							propchecktype = SCA_PropertySensor::KX_PROPSENSOR_EQUAL;
							break;
						case SENS_PROP_NEQUAL:
							propchecktype = SCA_PropertySensor::KX_PROPSENSOR_NOTEQUAL;
							break;
						case SENS_PROP_INTERVAL:
							propchecktype = SCA_PropertySensor::KX_PROPSENSOR_INTERVAL;
							break;
						case SENS_PROP_CHANGED:
							propchecktype = SCA_PropertySensor::KX_PROPSENSOR_CHANGED;
							break;
						case SENS_PROP_EXPRESSION:
							propchecktype = SCA_PropertySensor::KX_PROPSENSOR_EXPRESSION;
							/* error */
							break;
						case SENS_PROP_LESSTHAN:
							propchecktype = SCA_PropertySensor::KX_PROPSENSOR_LESSTHAN;
							break;
						case SENS_PROP_GREATERTHAN:
							propchecktype = SCA_PropertySensor::KX_PROPSENSOR_GREATERTHAN;
							break;
						default:
							; /* error */
						}
						gamesensor = new SCA_PropertySensor(eventmgr,gameobj,propname,propval,propmaxval,propchecktype);
					}

					break;
				}
			case SENS_ACTUATOR:
				{
					bActuatorSensor* blenderactsensor = (bActuatorSensor*) sens->data;
					// we will reuse the property event manager, there is nothing special with this sensor
					SCA_EventManager* eventmgr
						= logicmgr->FindEventManager(SCA_EventManager::ACTUATOR_EVENTMGR);
					if (eventmgr)
					{
						STR_String propname=blenderactsensor->name;
						gamesensor = new SCA_ActuatorSensor(eventmgr,gameobj,propname);
					}
					break;
				}

			case SENS_ARMATURE:
				{
					bArmatureSensor* blenderarmsensor = (bArmatureSensor*) sens->data;
					// we will reuse the property event manager, there is nothing special with this sensor
					SCA_EventManager* eventmgr
						= logicmgr->FindEventManager(SCA_EventManager::BASIC_EVENTMGR);
					if (eventmgr)
					{
						STR_String bonename=blenderarmsensor->posechannel;
						STR_String constraintname=blenderarmsensor->constraint;
						gamesensor = new KX_ArmatureSensor(eventmgr,gameobj,bonename,constraintname, blenderarmsensor->type, blenderarmsensor->value);
					}
					break;
				}

			case SENS_RADAR:
				{

					SCA_EventManager* eventmgr = logicmgr->FindEventManager(SCA_EventManager::TOUCH_EVENTMGR);
					if (eventmgr)
					{
						bRadarSensor* blenderradarsensor = (bRadarSensor*) sens->data;
						const STR_String radarpropertyname = blenderradarsensor->name;

						int radaraxis = blenderradarsensor->axis;

						MT_Scalar coneheight = blenderradarsensor->range;

						// janco: the angle was doubled, so should I divide the factor in 2
						// or the blenderradarsensor->angle?
						// nzc: the angle is the opening angle. We need to init with
						// the axis-hull angle,so /2.0.
						MT_Scalar factor = tan(blenderradarsensor->angle * 0.5f);
						//MT_Scalar coneradius = coneheight * (factor / 2);
						MT_Scalar coneradius = coneheight * factor;


						// this sumoObject is not deleted by a gameobj, so delete it ourself
						// later (memleaks)!
						MT_Scalar smallmargin = 0.0;
						MT_Scalar largemargin = 0.0;

						bool bFindMaterial = false;
						PHY_IPhysicsController* ctrl = kxscene->GetPhysicsEnvironment()->CreateConeController((float)coneradius, (float)coneheight);

						gamesensor = new KX_RadarSensor(
							eventmgr,
							gameobj,
							ctrl,
							coneradius,
							coneheight,
							radaraxis,
							smallmargin,
							largemargin,
							bFindMaterial,
							radarpropertyname);

					}

					break;
				}
			case SENS_RAY:
				{
					bRaySensor* blenderraysensor = (bRaySensor*) sens->data;

					//blenderradarsensor->angle;
					SCA_EventManager* eventmgr = logicmgr->FindEventManager(SCA_EventManager::BASIC_EVENTMGR);
					if (eventmgr)
					{
						bool bFindMaterial = (blenderraysensor->mode & SENS_COLLISION_MATERIAL);
						bool bXRay = (blenderraysensor->mode & SENS_RAY_XRAY);

						STR_String checkname = (bFindMaterial? blenderraysensor->matname : blenderraysensor->propname);

						// don't want to get rays of length 0.0 or so
						double distance = (blenderraysensor->range < 0.01f ? 0.01f : blenderraysensor->range);
						int axis = blenderraysensor->axisflag;


						gamesensor = new KX_RaySensor(eventmgr,
													  gameobj,
													  checkname,
													  bFindMaterial,
													  bXRay,
													  distance,
													  axis,
													  kxscene);

					}
					break;
				}

			case SENS_RANDOM:
				{
					bRandomSensor* blenderrndsensor = (bRandomSensor*) sens->data;
					// some files didn't write randomsensor, avoid crash now for NULL ptr's
					if (blenderrndsensor)
					{
						SCA_EventManager* eventmgr = logicmgr->FindEventManager(SCA_EventManager::BASIC_EVENTMGR);
						if (eventmgr)
						{
							int randomSeed = blenderrndsensor->seed;
							if (randomSeed == 0)
							{
								randomSeed = (int)(kxengine->GetRealTime()*100000.0);
								randomSeed ^= (intptr_t)blenderrndsensor;
							}
							gamesensor = new SCA_RandomSensor(eventmgr, gameobj, randomSeed);
						}
					}
					break;
				}
			case SENS_JOYSTICK:
				{
					int joysticktype = SCA_JoystickSensor::KX_JOYSENSORMODE_NODEF;

					bJoystickSensor* bjoy = (bJoystickSensor*) sens->data;

					SCA_JoystickManager *eventmgr
						= (SCA_JoystickManager*) logicmgr->FindEventManager(SCA_EventManager::JOY_EVENTMGR);
					if (eventmgr)
					{
						int axis	=0;
						int axisf	=0;
						int button	=0;
						int hat		=0;
						int hatf	=0;
						int prec	=0;

						switch (bjoy->type) {
						case SENS_JOY_AXIS:
							axis	= bjoy->axis;
							axisf	= bjoy->axisf;
							prec	= bjoy->precision;
							joysticktype  = SCA_JoystickSensor::KX_JOYSENSORMODE_AXIS;
							break;
						case SENS_JOY_BUTTON:
							button	= bjoy->button;
							joysticktype  = SCA_JoystickSensor::KX_JOYSENSORMODE_BUTTON;
							break;
						case SENS_JOY_HAT:
							hat		= bjoy->hat;
							hatf	= bjoy->hatf;
							joysticktype  = SCA_JoystickSensor::KX_JOYSENSORMODE_HAT;
							break;
						case SENS_JOY_AXIS_SINGLE:
							axis	= bjoy->axis_single;
							prec	= bjoy->precision;
							joysticktype  = SCA_JoystickSensor::KX_JOYSENSORMODE_AXIS_SINGLE;
							break;
						default:
							printf("Error: bad case statement\n");
							break;
						}
						gamesensor = new SCA_JoystickSensor(
							eventmgr,
							gameobj,
							bjoy->joyindex,
							joysticktype,
							axis,axisf,
							prec,
							button,
							hat,hatf,
							(bjoy->flag & SENS_JOY_ANY_EVENT));
					}
					else
					{
						printf("Error there was a problem finding the event manager\n");
					}

					break;
				}
			default:
				{
				}
			}

			if (gamesensor)
			{
				gamesensor->SetExecutePriority(executePriority++);
				STR_String uniquename = sens->name;
				uniquename += "#SENS#";
				uniqueint++;
				CIntValue* uniqueval = new CIntValue(uniqueint);
				uniquename += uniqueval->GetText();
				uniqueval->Release();

				/* Conversion succeeded, so we can set the generic props here.   */
				gamesensor->SetPulseMode(pos_pulsemode,
										 neg_pulsemode,
										 skipped_ticks);
				gamesensor->SetInvert(invert);
				gamesensor->SetLevel(level);
				gamesensor->SetTap(tap);
				gamesensor->SetName(sens->name);
				gamesensor->SetLogicManager(logicmgr);

				gameobj->AddSensor(gamesensor);

				// only register to manager if it's in an active layer
				// Make registration dynamic: only when sensor is activated
				//if (isInActiveLayer)
				//	gamesensor->RegisterToManager();

				gamesensor->ReserveController(sens->totlinks);
				for (int i=0;i<sens->totlinks;i++)
				{
					bController* linkedcont = (bController*) sens->links[i];
					if (linkedcont) {
						// If the controller is deactived doesn't register it
						if (!(linkedcont->flag & CONT_DEACTIVATE)) {
							SCA_IController* gamecont = converter->FindGameController(linkedcont);

							if (gamecont) {
								logicmgr->RegisterToSensor(gamecont,gamesensor);
							}
							else {
								printf("Warning, sensor \"%s\" could not find its controller "
									   "(link %d of %d) from object \"%s\"\n"
									   "\tthere has been an error converting the blender controller for the game engine,"
									   "logic may be incorrect\n", sens->name, i+1, sens->totlinks, blenderobject->id.name+2);
							}
						}
					}
					else {
						printf("Warning, sensor \"%s\" has lost a link to a controller "
							   "(link %d of %d) from object \"%s\"\n"
							   "\tpossible causes are partially appended objects or an error reading the file,"
							   "logic may be incorrect\n", sens->name, i+1, sens->totlinks, blenderobject->id.name+2);
					}
				}
				// special case: Keyboard sensor with no link
				// this combination is usually used for key logging.
				if (sens->type == SENS_KEYBOARD && sens->totlinks == 0) {
					// Force the registration so that the sensor runs
					gamesensor->IncLink();
				}

				// done with gamesensor
				gamesensor->Release();

			}
		}

		sens=sens->next;
	}
}

