/*
 * ***** 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_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 "IntValue.h"
#include "KX_BlenderKeyboardDevice.h"
#include "KX_BlenderGL.h"
#include "RAS_ICanvas.h"
#include "PHY_IPhysicsEnvironment.h"

#include "KX_KetsjiEngine.h"
#include "KX_BlenderSceneConverter.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 frequency = 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) {
		SCA_ISensor* gamesensor=NULL;
		/* All sensors have a pulse toggle, frequency, 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;
		
		frequency = 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);
					
					
					STR_String touchPropOrMatName = ( bFindMaterial ? 
						blendertouchsensor->materialName:
					(blendertouchsensor->name ? blendertouchsensor->name: ""));
					
					
					if (gameobj->GetPhysicsController())
					{
						gamesensor = new KX_TouchSensor(eventmgr,
							gameobj,
							bFindMaterial,
							bTouchPulse,
							touchPropOrMatName);
					}
					
				}
				
				break;
			}
		case SENS_TOUCH:
			{
				SCA_EventManager* eventmgr = logicmgr->FindEventManager(SCA_EventManager::TOUCH_EVENTMGR);
				if (eventmgr)
				{
					STR_String touchpropertyname;
					bTouchSensor* blendertouchsensor = (bTouchSensor*)sens->data;
					
					if (blendertouchsensor->ma)
					{
						touchpropertyname = (char*) (blendertouchsensor->ma->id.name+2);
					}
					bool bFindMaterial = true;
					if (gameobj->GetPhysicsController())
					{
						gamesensor = new KX_TouchSensor(eventmgr,
							gameobj,
							bFindMaterial,
							false,
							touchpropertyname);
					}
				}
				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 */
					STR_String subject = (msgSens->subject
						? (char*)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)
				{
					STR_String nearpropertyname;
					bNearSensor* blendernearsensor = (bNearSensor*)sens->data;
					if (blendernearsensor->name)
					{
						// only objects that own this property will be taken into account
						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 */
						gamesensor = new KX_MouseFocusSensor(eventmgr,
							startx,
							starty,
							keytype,
							trackfocus,
							(bmouse->flag & SENS_MOUSE_FOCUS_PULSE) ? true:false,
							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;
					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)
				{
					STR_String radarpropertyname;
					STR_String touchpropertyname;
					bRadarSensor* blenderradarsensor = (bRadarSensor*) sens->data;
					
					int radaraxis = blenderradarsensor->axis;
					
					if (blenderradarsensor->name)
					{
						// only objects that own this property will be taken into account
						radarpropertyname = (char*) blenderradarsensor->name;
					}
					
					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(MT_radians((blenderradarsensor->angle) / 2.0f));
					//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,
			                         frequency);
			gamesensor->SetInvert(invert);
			gamesensor->SetLevel(level);
			gamesensor->SetTap(tap);
			gamesensor->SetName(sens->name);
			
			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) {
					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;
	}
}

