/*
   CMSC 427 - Term Project Candidate
   ---------------------------------

   Beth Weinstein - bweinste@wam.umd.edu
   Ben Stewart - stewartb@wam.umd.edu
   Nawfel Tricha - Topaz@endorproductions.com

   Minimal OpenGL project using Microsoft Visual C++.
   This project is a premilinary candidate for the term 
   project required by CMSC 427.

   ABSTRACT:
      In this project, we will develop several fast 
      rendering and collision routines which would 
      require C/C++ speed and accuracy and then 
      implement a VisualJ++ interface.

   MODULE:
      Sling Shot Transformer Alias module.
*/

#include "global.h"
#include <math.h>

// BS: constants referencing model, texture and keyframes
#define SLING_SHOT_MODEL "c:/DiscoStu/gfx/SlingShot/tris.md2"
#define SLING_SHOT_TEXTURE "c:/DiscoStu/gfx/SlingShot/slingshot.tga"
#define STAND				0
#define TRNS_FRM_DOWN		1
#define FLY					2
#define TRNS_FRM_UP			3
#define CIRCLE				4
#define DANCE				5
#define JUMP				6
#define STAND_FRAME_START	84
#define STAND_FRAME_MID		89
#define STAND_FRAME_END		94
#define TRNS_FRM_DOWN_START 208
#define TRNS_FRM_DOWN_MID   217
#define TRNS_FRM_DOWN_END   217
#define FLY_START			218
#define FLY_MID				218
#define FLY_END				218
#define TRNS_FRM_UP_START	218
#define TRNS_FRM_UP_MID		228
#define TRNS_FRM_UP_END		228
#define CIRCLE_START		217
#define CIRCLE_MID			217
#define CIRCLE_END			217
#define DANCE_START			72
#define DANCE_MID			77
#define DANCE_END			83
#define JUMP_START			66
#define JUMP_MID			71
#define JUMP_END			71

#define STRAIGHT			1
#define B_ROLL				2
#define H_CIRCLE			4
#define V_CIRCLE			8
#define SPIN				16

static unsigned int	fly_mode = 0;
static vec3_t destination;

#define X_BOUND				350 * 0.4f
#define Z_BOUND				350 * 0.4f

void SlingShotDanceThink(edict_t *self);
void SlingShotFlyThink(edict_t *self);

// BS: Some Data to drive the Sling Shot character through animations and flying
m_data_t SlingShotData =  
{
	7,
	STAND,
	{STAND, TRNS_FRM_DOWN, FLY, TRNS_FRM_UP, CIRCLE, DANCE, JUMP}, 
    {STAND_FRAME_START, TRNS_FRM_DOWN_START, FLY_START, 
	 TRNS_FRM_UP_START, CIRCLE_START, DANCE_START, JUMP_START},
	{STAND_FRAME_MID, TRNS_FRM_DOWN_MID, FLY_MID, 
	 TRNS_FRM_UP_MID, CIRCLE_MID, DANCE_MID, JUMP_MID},
	{STAND_FRAME_END, TRNS_FRM_DOWN_END, FLY_END, 
	 TRNS_FRM_UP_END, CIRCLE_END, DANCE_END, JUMP_END},
	0.5f,
	{0, 1, 0},
	15,
	0,
	{0, 10, 12},
	{0, 0, 0},
	{-142, 19, -140}
};

static int direction = -1;

static int direction_x = 1;
static int direction_z = 1;
static float cam_spin;
/*
	BS: Position Camera on Sling Shot
*/
void SlingShotPosCamera(struct edict_s *self)
{
	float cam_offset;
	static float spin_delta = (float) SlingShotData.camera_spin_delta;
	vec3_t arrow = {0, 0, 0};

	glLoadIdentity();

	/*
	BS: Depending upon which move SLing SHot is acting out we place the camera
		differently
	*/

	switch(SlingShotData.curr_move)
	{
	case STAND:
	case DANCE:
	case JUMP:
	case TRNS_FRM_UP:
	case TRNS_FRM_DOWN:
		arrow[1] = cam_spin;

		/*
			To preserve the smoke effect, the camera must be rotated in the
			same order every time, hence the LookAt function is not sufficient
			enough for camera orientation
		*/
		AngleVectors(arrow, arrow, NULL, NULL);
		VectorMA(arrow, SlingShotData.displacement_factor, self->origin, cam.origin);
		VectorSubtract(self->origin, cam.origin, arrow);
		VectorAngles(arrow, cam.angle);

		glRotatef(-cam.angle[PITCH], 1, 0, 0);
		glRotatef(-cam.angle[YAW], 0, 1, 0);
		glRotatef(-cam.angle[ROLL], 0, 0, 1);
		glTranslatef(-cam.origin[0], -cam.origin[1], -cam.origin[2]);      

		cam_offset = (cam_spin - self->angle[YAW]);
		if((cam_offset > 45) || (cam_offset < -45))
		{
			 spin_delta = -spin_delta;
		}
		cam_spin += spin_delta;
		break;
	case FLY:
		/* Watch from on the ground or sitting in the cockpit */
		if(fly_mode & B_ROLL)
		{
			VectorSet(arrow, 0, 5, 0);
			VectorCopy(arrow, cam.origin);
			VectorSubtract(self->origin, arrow, arrow);
			VectorAngles(arrow, cam.angle);
		}
		else 
		{
			VectorCopy(self->origin, cam.origin);
			VectorCopy(self->angle, cam.angle);
		}

		glRotatef(-cam.angle[PITCH], 1, 0, 0);
		glRotatef(-cam.angle[YAW], 0, 1, 0);
		glRotatef(-cam.angle[ROLL], 0, 0, 1);

		glTranslatef(-cam.origin[0], -cam.origin[1], -cam.origin[2]);      
		break;
	}
}

/*
   BS:
   Listen to DSP.
   There are five versions that drive the different moves of Sling Shot
   1 <- attempts to make SLing Shot dance to beat
   2 <- drives transform to plane animation
   4 <- drives transform to robot animation
*/
void SlingShotListen1(edict_t *self, struct winampVisModule *this_mod)
{
	static int direction = 1;
   register float kick = dsp.kick;

   if(kick > 0.5)
   {
	   if(direction > 0)
	   {
		   self->frame = SlingShotData.move_begin_frames[SlingShotData.curr_move];
		   self->end_frame = SlingShotData.move_mid_frames[SlingShotData.curr_move];
		   direction = -1;
	   }
	   else
	   {
		   self->end_frame = SlingShotData.move_end_frames[SlingShotData.curr_move];
		   self->loop_frame = SlingShotData.move_begin_frames[SlingShotData.curr_move];
	   }

   }
   else
   {
	   if(direction < 0)
	   {
		   self->end_frame = SlingShotData.move_end_frames[SlingShotData.curr_move];
		   self->loop_frame = -1;
		   direction = 1;
	   }
   }
}

void SlingShotListen2(edict_t *self, struct winampVisModule *this_mod)
{
	if((unsigned int) self->frame == SlingShotData.move_end_frames[SlingShotData.curr_move])
	{
		self->velocity[1] = 0.5f;
	}
}

/*
	BS: Listen to DSP
*/
void SlingShotListen4(edict_t *self, struct winampVisModule *this_mod)
{
	if(self->origin[1] <= 19)
	{
		self->origin[1] = 19;
		self->frame = SlingShotData.move_begin_frames[SlingShotData.curr_move];
		self->next_frame = SlingShotData.move_begin_frames[SlingShotData.curr_move] - 1;
		self->end_frame = SlingShotData.move_end_frames[SlingShotData.curr_move];
		self->loop_frame = -1;
		self->think = SlingShotDanceThink;
		SlingShotData.curr_move = DANCE;
		self->listen = NULL;
		self->next_think = eng.time + 1000;
		self->velocity[1] = 0;
	}
}


/* Main listen function when sling shot is flying */
void SlingShotFly(edict_t *self, struct winampVisModule *this_mod)
{
	static float end_angle;
	static float velocity_x;
	static float velocity_z;
	static unsigned int old_fly_mode = fly_mode;
	static vec3_t arrow;

	if(fly_mode & B_ROLL)
	{
		self->avelocity[ROLL] = 5.0f;
	}
	if(fly_mode & STRAIGHT)
	{

	   /* SlingShot changes direction when he hits the edge of the disco */
		VectorSubtract(destination, self->origin, arrow);
		if(DotProduct(arrow, arrow) < 49)
		{
			VectorClear(arrow);
			VectorClear(self->avelocity);
			self->angle[ROLL] = 0;
			VectorSubtract(arrow, destination, arrow);   
			end_angle = VectorYaw(arrow);
			VectorCopy(destination, self->origin);
			direction = (self->origin[0] < 0) ? 1 : -1;
			self->avelocity[YAW] = direction * 2.5f;
			old_fly_mode = fly_mode = 0;
			fly_mode |= SPIN;
			velocity_x = 0;
			velocity_z = 0;
		    self->velocity[0] = 0;
		    self->velocity[2] = 0;	
		}
		else if(self->origin[0] > X_BOUND)
		{
			VectorClear(self->avelocity);
			self->angle[ROLL] = 0;
			direction_x = -1;
   			self->avelocity[YAW] = -direction_z * 2.5;
			direction = -direction_z;
			self->velocity[0] = -self->velocity[0];
			end_angle = VectorYaw(self->velocity);
	   
		   if(direction_z < 0)
		   {
				self->angle[YAW] -= 360;
		   }
		   old_fly_mode = fly_mode;
		   fly_mode = 0;
		   fly_mode |= SPIN;	

		   velocity_x = self->velocity[0];
		   velocity_z = self->velocity[2];
		   self->velocity[0] = 0;
		   self->velocity[2] = 0;	

		}
		else if(self->origin[0] < -X_BOUND)
		{
			VectorClear(self->avelocity);
			self->angle[ROLL] = 0;
			direction_x = 1;
   			self->avelocity[YAW] = direction_z * 2.5;
		    direction = direction_z;
			self->velocity[0] = -self->velocity[0];
			end_angle = VectorYaw(self->velocity);
			if(direction_z < 0)
			{
				self->angle[YAW] += 360;
			}

			old_fly_mode = fly_mode;
		    fly_mode = 0;
			fly_mode |= SPIN;
		    velocity_x = self->velocity[0];
		    velocity_z = self->velocity[2];
			self->velocity[0] = 0;
			self->velocity[2] = 0;	
		}

		else if(self->origin[2] > Z_BOUND)
		{
			VectorClear(self->avelocity);
			self->angle[ROLL] = 0;
			direction_z = -1;
			self->avelocity[YAW] = direction_x * 2.5;
			direction = direction_x;
			self->velocity[2] = -self->velocity[2];
   			end_angle = VectorYaw(self->velocity);

			old_fly_mode = fly_mode;
		    fly_mode = 0;
			fly_mode |= SPIN;
		    velocity_x = self->velocity[0];
		    velocity_z = self->velocity[2];
			self->velocity[0] = 0;
			self->velocity[2] = 0;	

		}
		else if(self->origin[2] < -Z_BOUND)
		{
			VectorClear(self->avelocity);
			self->angle[ROLL] = 0;
			direction_z = 1;
			self->avelocity[YAW] = -direction_x * 2.5;
			direction = -direction_x;
   			self->velocity[2] = -self->velocity[2];//direction_z;
   			end_angle = VectorYaw(self->velocity);
			
			old_fly_mode = fly_mode;
		    fly_mode = 0;
			fly_mode |= SPIN;
		    
			velocity_x = self->velocity[0];
		    velocity_z = self->velocity[2];
			self->velocity[0] = 0;
			self->velocity[2] = 0;	
		}
		else
		{
			self->angle[YAW] = VectorYaw(self->velocity);
		}
	}
	/* Let's fly in a vertical circle */
	if(fly_mode & V_CIRCLE)
	{
		/* We'll start flying in a vertical circle once we get close to the origin */
		VectorSet(arrow, 0, self->origin[1], 0);
		VectorSubtract(self->origin, arrow, arrow);
		/* Once we get moving, update the velocity.  ONce he's circled twice,
			stop a start flying forward again
		*/
		if(self->angle[PITCH] > 0)
		{
			self->velocity[1] = 3 * sin(self->angle[PITCH] * (M_PI / 180));
			self->velocity[0] = 3 * direction_x * cos(self->angle[PITCH] * (M_PI / 180));
			self->velocity[2] = 3 * direction_z * cos(self->angle[PITCH] * (M_PI / 180));
			if(self->angle[PITCH] >= 720)
			{
				self->angle[PITCH] = 0;
				self->avelocity[PITCH] = 0;
				fly_mode &= ~V_CIRCLE;
				fly_mode |= STRAIGHT;
				VectorClear(self->velocity);
				VectorClear(self->avelocity);
				self->velocity[0] = velocity_x;
				self->velocity[2] = velocity_z;
			}
		}
		else if(DotProduct(arrow, arrow) < 16)
		{
			fly_mode &= ~STRAIGHT;
			self->avelocity[PITCH] = 3.0f;
			velocity_x = self->velocity[0];
			velocity_z = self->velocity[2];
		}

	}
	/* Sling Shot turns around once he gets to the opposite speaker */
	if(fly_mode & SPIN)
	{
	   if(direction < 0)
	   {
		   if(self->angle[YAW] < end_angle)
		   {
		   	   self->angle[YAW] = end_angle;
			   self->avelocity[YAW] = 0;
			   fly_mode = 0;
			   fly_mode |= old_fly_mode;
			   self->velocity[0] = velocity_x;
			   self->velocity[2] = velocity_z;
			   if((velocity_x == 0) && (velocity_z == 0))
			   {
				   SlingShotData.curr_move = TRNS_FRM_UP;
				   self->think(self);
				   cam_spin = self->angle[YAW];
			   }
		   }
	   }
	   else
	   {
   		   if(self->angle[YAW] > end_angle)
		   {
		   	   self->angle[YAW] = end_angle;
			   self->avelocity[YAW] = 0;
			   fly_mode = 0;
			   fly_mode |= old_fly_mode;
			   self->velocity[0] = velocity_x;
			   self->velocity[2] = velocity_z;
   			   if((velocity_x == 0) && (velocity_z == 0))
			   {
				   SlingShotData.curr_move = TRNS_FRM_UP;
				   self->think(self);
				   cam_spin = self->angle[YAW];
			   }
		   }
	   }
	}
}

/*
// fly in circle horizontal to ground
	self->velocity[0] = 5 * -sin(self->angle[YAW] * (M_PI / 180));
	self->velocity[2] = 5 * -cos(self->angle[YAW] * (M_PI / 180));
*/
/*
//  fly in circle perpendicular to ground
	self->velocity[1] = 5 * sin(self->angle[PITCH] * (M_PI / 180));
	self->velocity[0] = 5 * direction_x * cos(self->angle[PITCH] * (M_PI / 180));
	self->velocity[2] = 5 * direction_z * cos(self->angle[PITCH] * (M_PI / 180));
*/


/*
   BS:
   Render Sling Shot
*/
void SlingShotRender(edict_t *self)
{
   if (last_texture != self->skin_index)
   {
      glBindTexture (GL_TEXTURE_2D, self->skin_index); 
      last_texture = self->skin_index;
   }   
   glEnable(GL_DEPTH_TEST);
   RenderAliasModel(self);
   glDisable(GL_DEPTH_TEST);
}

/*
	BS: 
	Think function for when SlingShot is Dancing.  Randomly selects a move
*/
void SlingShotDanceThink(edict_t *self)
{
	self->listen = SlingShotListen1;
	switch(rand() % 4)
	{
	case 0:
		SlingShotData.curr_move = STAND;
		self->next_think = eng.time + 8500;
		break;
	case 1:
		SlingShotData.curr_move = DANCE;
		self->next_think = eng.time + 15000;
		break;
	case 2:
		SlingShotData.curr_move = JUMP;
		self->next_think = eng.time + 5000;
		break;
	case 3:
		SlingShotData.curr_move = TRNS_FRM_DOWN;
		self->listen = SlingShotListen2;
		self->think = SlingShotFlyThink;
		self->next_think = eng.time + 1000;
		break;
	}

	self->next_frame = SlingShotData.move_begin_frames[SlingShotData.curr_move];
	self->loop_frame = -1;
	self->end_frame = SlingShotData.move_end_frames[SlingShotData.curr_move];
}

/*
	BS:
	Think function for when SlingShot is flying
*/
void SlingShotFlyThink(edict_t *self)
{
	int num;

	switch(SlingShotData.curr_move)
	{
		case TRNS_FRM_DOWN:
			if(self->origin[1] > 19)
			{
				SlingShotData.curr_move = FLY;
				self->think(self);
			}
			else
			{
				SlingShotData.displacement_factor = 15;
				self->listen = SlingShotListen2;
				self->next_think = eng.time + 5000;
			}
			break;
		case FLY:
			SlingShotData.displacement_factor = 1;
			fly_mode = 0;
			fly_mode |= STRAIGHT;
			num = (rand() % 10);
			if(num >= 7)
			{
				fly_mode |= V_CIRCLE;
			}
			else if(num <= 2)
			{
				fly_mode |= B_ROLL;
			}
			self->listen = SlingShotFly;
			/* Set a destination and make a B-line for it! */
			direction_x = (self->origin[0] < 0) ? 1 : -1;
			direction_z = (self->origin[2] < 0) ? 1 : -1;
			destination[0] = -self->origin[0];
			destination[1] = self->origin[1];
			destination[2] = -self->origin[2];
			self->velocity[0] = 2 * (direction_x);
			self->velocity[1] = 0;
			self->velocity[2] = 2 * (direction_z);
			self->frame = SlingShotData.move_begin_frames[SlingShotData.curr_move];
			self->next_think = 0;
			break;
		case TRNS_FRM_UP:
			/* Land and transform! */ 
			self->next_think = 0;
			SlingShotData.displacement_factor = 15;
			self->angle[ROLL] = 0;
			self->listen = SlingShotListen4;
			self->velocity[0] = 0;
			self->velocity[1] = -0.5;
			self->velocity[2] = 0;
			self->angle[ROLL] = 0;
			self->angle[PITCH] = 0;
			VectorClear(self->avelocity);
			self->frame = SlingShotData.move_begin_frames[SlingShotData.curr_move];
			self->next_frame = SlingShotData.move_begin_frames[SlingShotData.curr_move];
			self->end_frame = SlingShotData.move_begin_frames[SlingShotData.curr_move];
			self->loop_frame = -1;
			break;
		case DANCE:
			self->think = SlingShotDanceThink;
			self->think(self);
			break;
		default:
			break;

	}
}

/*
   BS:
   Spawn Sling Shot
*/
edict_t *SlingShotSpawn()
{
   static unsigned int skinned = NOT_FOUND, modeled = NOT_FOUND;
   int model_index;
   edict_t *ent;
   skin_t *skin;
   ent = EdictSpawn();
   vec3_t arrow = {0, 0, 0};

   if (modeled == NOT_FOUND)
      {
      if ((model_index = CMD2Exist(SLING_SHOT_MODEL)) == NOT_FOUND)
         {
         ent->model = new CMD2;
         if (CMD2Spawn(SLING_SHOT_MODEL, ent->model) == NOT_FOUND)
            return NULL;
         if (ent->model->Read(SLING_SHOT_MODEL) != 1)
            EngineError("SpawnBobaFett: Error reading Alias md2 '%s'", SLING_SHOT_MODEL);
         }
      else
         {
         ent->model = g_CMD2s[model_index]->model;
         }
      }
   else
      ent->model = g_CMD2s[modeled]->model;

   // BS: Only load skins if they are not allocated already:
   if (skinned == NOT_FOUND)
      {
      // BS: Does skin exist in library?
      if ((ent->skin_num = SkinIndex(SLING_SHOT_TEXTURE)) == NOT_FOUND)
         {
         int ix, iy;
         skin = new skin_t;
         memset(skin, 0, sizeof(*skin));
         ent->skin_num = SkinSpawn(SLING_SHOT_TEXTURE, skin);
         if (!LoadTGA(SLING_SHOT_TEXTURE, &skin->tga, skin->width, skin->height))
            EngineError("TGA LOAD: Skin '%s' could not be loaded.\n", SLING_SHOT_TEXTURE);
         ent->skin_index = eng.skin_index++;
         glBindTexture (GL_TEXTURE_2D, ent->skin_index);
         TGA2GLTexture (skin, 0, 255, ix, iy);
	      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	      glTexImage2D (GL_TEXTURE_2D, 0, 4, ix, iy, 0, GL_RGBA, GL_UNSIGNED_BYTE, skin->glTexture);
         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	      glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	      glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_DONT_CARE);	
         skin->index = ent->skin_index;
         skinned = ent->skin_index;
		 UpdateTextureCoordinates(ent->model, skin);
         InitGL();      
         }
      else // Just use skin from already loaded list:
         ent->skin_index = g_skins[ent->skin_num]->skin->index;
      }
   else
      ent->skin_index = skinned;

   // State of entity:
   ent->state = ENT_ST_USED | ENT_ST_PHYS | ENT_ST_VISIBLE;

   ent->origin[0] = SlingShotData.m_start[0];
   ent->origin[1] = SlingShotData.m_start[1];
   ent->origin[2] = SlingShotData.m_start[2];
   ent->rgba[0] = 1;
   ent->rgba[1] = 1;
   ent->rgba[2] = 1;
   ent->rgba[3] = 1;
   ent->avelocity[PITCH] = 0;
   ent->avelocity[ROLL] = 0;
   ent->avelocity[YAW] = 0;
   ent->angle[PITCH] = 0;
   ent->frame = SlingShotData.move_begin_frames[SlingShotData.curr_move];
   ent->loop_frame = -1;
   ent->next_frame = SlingShotData.move_begin_frames[SlingShotData.curr_move];
   ent->end_frame = SlingShotData.move_mid_frames[SlingShotData.curr_move];
   ent->next_think = eng.time + 5000;
   ent->scale = 0.3f;
   ent->render = SlingShotRender;
   ent->listen = SlingShotListen1;
   ent->think = SlingShotDanceThink;
   ent->camera = SlingShotPosCamera;

   VectorSubtract(arrow, ent->origin, arrow);   
   ent->angle[YAW] = VectorYaw(arrow);
   cam_spin = ent->angle[YAW];

   return ent;
}