/***************************************************************************
 * collison.cpp  -  internal collision functions
 *
 * Copyright (C) 2005 - 2008 Florian Richter
 * Copyright (C) 2005        Amir Taaki
 ***************************************************************************/
/*
   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 3 of the License, or
   (at your option) any later version.
   
   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "../core/collision.h"
#include "../core/game_core.h"
#include "../objects/movingsprite.h"
#include "../level/level.h"
#include "../player/player.h"
#include "../video/gl_surface.h"
#include "../core/sprite_manager.h"

/* *** *** *** *** *** *** *** collision type class *** *** *** *** *** *** *** *** *** *** */

cObjectCollisionType :: cObjectCollisionType( void )
{

}

cObjectCollisionType :: ~cObjectCollisionType( void )
{
	list.clear();
}

void cObjectCollisionType :: add( cSprite *obj )
{
	if( !obj )
	{
		return;
	}

	list.push_back( obj );
}

bool cObjectCollisionType :: find( const cSprite *obj )
{
	for( SpriteList::iterator itr = list.begin(), itr_end = list.end(); itr != itr_end; ++itr )
	{
		// matching
		if( (*itr) == obj )
		{
			return 1;
		}
	}

	return 0;
}

cSprite *cObjectCollisionType :: find( const ArrayType type )
{
	for( SpriteList::iterator itr = list.begin(), itr_end = list.end(); itr != itr_end; ++itr )
	{
		// matching
		if( (*itr)->sprite_array == type )
		{
			return (*itr);
		}
	}

	return 0;
}

cSprite *cObjectCollisionType :: find( const SpriteType type )
{
	for( SpriteList::iterator itr = list.begin(), itr_end = list.end(); itr != itr_end; ++itr )
	{
		// matching
		if( (*itr)->type == type )
		{
			return (*itr);
		}
	}

	return 0;
}

unsigned int cObjectCollisionType :: size( void )
{
	return list.size();
}

/* *** *** *** *** *** *** *** collision description *** *** *** *** *** *** *** *** *** *** */

cObjectCollision :: cObjectCollision( void )
{
	received = 0;
	type = CO_NOTHING;
	number = 0;
	direction = DIR_UNDEFINED;
}

cObjectCollision :: ~cObjectCollision( void )
{
	//
}

void cObjectCollision :: Set_Direction( cSprite *base, cSprite *col )
{
	direction = Get_Collision_Direction( base, col );
}

GL_rect cObjectCollision :: Get_Collision_Object_Rect( void )
{
	// player
	if( type == CO_PLAYER )
	{
		return pActive_Player->col_rect;
	}

	// sprite manager
	return pActive_Sprite_Manager->objects[number]->col_rect;
}

/* *** *** *** *** *** *** *** functions *** *** *** *** *** *** *** *** *** *** */

ObjectDirection Get_Collision_Direction( cSprite *base, cSprite *col )
{
	// if valid moving sprite
	if( base->sprite_array == ARRAY_ENEMY || base->sprite_array == ARRAY_ACTIVE )
	{
		const cMovingSprite *moving_base = static_cast<const cMovingSprite *>(base);

		// ## velocity based detection
		if( moving_base->velx != 0 || moving_base->vely != 0 )
		{
			// top
			if( moving_base->vely < 0 && Is_Collision_Top( &base->col_rect, &col->col_rect ) )
			{
				return DIR_TOP;
			}
			// bottom
			else if( moving_base->vely > 0 && Is_Collision_Bottom( &base->col_rect, &col->col_rect ) )
			{
				return DIR_BOTTOM;
			}
			// left
			else if( moving_base->velx < 0 && Is_Collision_Left( &base->col_rect, &col->col_rect ) )
			{
				return DIR_LEFT;
			}
			// right
			else if( moving_base->velx > 0 && Is_Collision_Right( &base->col_rect, &col->col_rect ) )
			{
				return DIR_RIGHT;
			}
		}
	}


	// ## detection without velocity

	// top
	if( Is_Collision_Top( &base->col_rect, &col->col_rect ) )
	{
		return DIR_TOP;
	}
	// bottom
	else if( Is_Collision_Bottom( &base->col_rect, &col->col_rect ) )
	{
		return DIR_BOTTOM;
	}
	// left
	else if( Is_Collision_Left( &base->col_rect, &col->col_rect ) )
	{
		return DIR_LEFT;
	}
	// right
	else if( Is_Collision_Right( &base->col_rect, &col->col_rect ) )
	{
		return DIR_RIGHT;
	}

	// ## advanced detection ( if base object is partly inside col object )

	// get the middle point
	GL_point point_base = base->col_rect.Get_pos_middle();
	GL_point point_col = col->col_rect.Get_pos_middle();

	// difference between points
	float diff_x = point_col.x - point_base.x;
	float diff_y = point_col.y - point_base.y;

	// more horizontal
	if( ( diff_x > 0 && ( diff_x > diff_y ) ) || ( diff_x < 0 && ( diff_x < diff_y ) ) )
	{
		// if col object point left
		if( diff_x > 0 )
		{
			return DIR_RIGHT;
		}
		// if col object point is right
		else
		{
			return DIR_LEFT;
		}
	}
	// more vertical
	else
	{
		// if col object point is on top
		if( diff_y > 0 )
		{
			return DIR_DOWN;
		}
		// if col object point is below
		else
		{
			return DIR_UP;
		}
	}
}

bool Is_Collision_Top( const GL_rect *base_rect, const GL_rect *col_rect )
{
	if( base_rect->y + 1 >= col_rect->y + col_rect->h && base_rect->x < col_rect->x + col_rect->w && base_rect->x + base_rect->w > col_rect->x )
	{
		return 1;
	}

	return 0;
}

bool Is_Collision_Bottom( const GL_rect *base_rect, const GL_rect *col_rect )
{
	if( base_rect->y + base_rect->h - 1 <= col_rect->y && base_rect->x < col_rect->x + col_rect->w && base_rect->x + base_rect->w > col_rect->x )
	{
		return 1;
	}

	return 0;
}

bool Is_Collision_Left( const GL_rect *base_rect, const GL_rect *col_rect )
{
	if( base_rect->x + 1 >= col_rect->x + col_rect->w && base_rect->y < col_rect->y + col_rect->h && base_rect->y + base_rect->h > col_rect->y )
	{
		return 1;
	}

	return 0;
}

bool Is_Collision_Right( const GL_rect *base_rect, const GL_rect *col_rect )
{
	if( base_rect->x + base_rect->w - 1 <= col_rect->x && base_rect->y < col_rect->y + col_rect->h && base_rect->y + base_rect->h > col_rect->y )
	{
		return 1;
	}

	return 0;
}

ObjectCollisionType Get_Collision_Type( ArrayType array_type )
{
	if( array_type == ARRAY_FRONT_PASSIVE || array_type == ARRAY_PASSIVE )
	{
		return CO_PASSIVE;
	}
	else if( array_type == ARRAY_MASSIVE )
	{
		return CO_MASSIVE;
	}
	else if( array_type == ARRAY_ACTIVE )
	{
		return CO_ACTIVE;
	}
	else if( array_type == ARRAY_ENEMY )
	{
		return CO_ENEMY;
	}

	return CO_NOTHING;
}

bool Col_Box( const GL_rect *a, const GL_rect *b )
{
	// check if their bounding boxes touch
	if( b->x + b->w < a->x )
	{
		return 0;
	}
	if( b->x > a->x + a->w )
	{
		return 0;
	}

	if( b->y + b->h < a->y )
	{
		return 0;
	}
	if( b->y > a->y + a->h )
	{
		return 0;
	}

	// bounding boxes intersect
	return 1;
}

bool Col_Box( const SDL_Rect *a, const GL_rect *b )
{
	// check if their bounding boxes touch
	if( b->x + b->w < a->x )
	{
		return 0;
	}
	if( b->x > a->x + a->w )
	{
		return 0;
	}

	if( b->y + b->h < a->y )
	{
		return 0;
	}
	if( b->y > a->y + a->h )
	{
		return 0;
	}

	// bounding boxes intersect
	return 1;
}

bool Col_Box_full( const SDL_Rect *a, const SDL_Rect *b )
{
	if( a->x <= b->x )
	{
		return 0;
	}
	if( a->x + a->w >= b->x + b->w )
	{
		return 0;
	}
	if( a->y <= b->y )
	{
		return 0;
	}
	if( a->y + a->h >= b->y + b->h )
	{
		return 0;
	}

	return 1;
}

// from SDL_collide ( Copyright (C) 2005 Amir Taaki ) - MIT License
bool Col_Circle( float x1, float y1, float r1, float x2, float y2, float r2, int offset )
{
	float xdiff = x2 - x1;	// x plane difference
	float ydiff = y2 - y1;	// y plane difference
	
	/* distance between the circles centres squared */
	float dcentre_sq = ( ydiff * ydiff ) + ( xdiff * xdiff );
	
	/* calculate sum of radiuses squared */
	float r_sum_sq = r1 + r2;	// square on seperate line, so
	r_sum_sq *= r_sum_sq;	// dont recompute r1 + r2

	return ( dcentre_sq - r_sum_sq <= ( offset * offset ) );
}

bool Col_Circle( cGL_Surface *surface1, SDL_Rect *a, cGL_Surface *surface2, SDL_Rect *b, int offset )
{
	return Col_Circle( surface1, a->x, a->y, surface2, b->x, b->y, offset );
}

// from SDL_collide ( Copyright (C) 2005 Amir Taaki ) - MIT License
bool Col_Circle( cGL_Surface *a, float x1, float y1, cGL_Surface *b, float x2, float y2, int offset )
{
	/* if radius is not specified
	we approximate them using SDL_Surface's
	width and height average and divide by 2*/
	float r1 = ( ( a->w + a->h ) / 4 );	// same as / 2) / 2;
	float r2 = ( ( b->w + b->h ) / 4 );

	x1 += a->w / 2;		// offset x and y
	y1 += a->h / 2;		// co-ordinates into
				// centre of image
	x2 += b->w / 2;
	y2 += b->h / 2;

	return Col_Circle( x1, y1, r1, x2, y2, r2, offset );
}

template<typename T> inline T pow2( T value ) { return value * value; }
template<typename T> inline T cl_min( T a, T b ) { if( a < b ) return a; return b; }

// from ClanLib ( Copyright (C) 2005 Magnus Norddahl ) - BSD License
float Distance_to_Line( float x, float y, GL_line *line )
{
	float L = sqrt( pow2( line->x2 - line->x1 ) + pow2( line->y2 - line->y1 ) );
	float r = ( ( x - line->x1 ) * ( line->x2 - line->x1 ) + ( y - line->y1 ) * ( line->y2 - line->y1 ) ) / pow2( L );
	
	if( r <= 0 || r >= 1 )
	{
		GL_point p( x, y );
		GL_point A( line->x1, line->y1 );
		GL_point B( line->x2, line->y2 );
		
		return cl_min( p.distance( A ), p.distance( B ) );
	}
	
	float s = ( ( line->y1 - y ) * ( line->x2 - line->x1 ) - ( line->x1 - x ) * ( line->y2 - line->y1 ) ) / pow2( L );
	return fabs( s ) * L ;
}

// from ClanLib ( Copyright (C) 2005 Magnus Norddahl ) - BSD License
GL_point Get_Line_Intersection( GL_line *line1, GL_line *line2 )
{
	float denominator = ( ( line1->x2 - line1->x1 ) * ( line2->y2 - line2->y1 ) - ( line1->y2 - line1->y1 ) * ( line2->x2 - line2->x1 ) );

	if( denominator == 0 )
	{
		return GL_point( line1->x1, line1->y1 );
	}
	
	float r = ( ( line1->y1 - line2->y1 ) * ( line2->x2 - line2->x1 ) - ( line1->x1 - line2->x1 ) * ( line2->y2 - line2->y1 ) ) / denominator;
	
	GL_point p;
	p.x = line1->x1 + r * ( line1->x2 - line1->x1 );
	p.y = line1->y1 + r * ( line1->y2 - line1->y1 );
	
	return p;
}

// from ClanLib ( Copyright (C) 2005 Magnus Norddahl ) - BSD License
bool Col_Line( GL_line *line1, GL_line *line2, bool collinear_intersect )
{
	float denominator = ( ( line1->x2 - line1->x1 ) * ( line2->y2 - line2->y1 ) - ( line1->y2 - line1->y1 ) * ( line2->x2 - line2->x1 ) );	
	
	if( denominator == 0 ) // parallel
	{
		if( ( line1->y1 - line2->y1 ) * ( line2->x2 - line2->x1 ) - ( line1->x1 - line2->x1 ) * ( line2->y2 - line2->y1 ) == 0 ) // collinear
		{
			if( collinear_intersect )
			{
				return 1;
			}
			else
			{
				return 0;
			}
		}

		return 0;
	}
	
	float r = ( ( line1->y1 - line2->y1 ) * ( line2->x2 - line2->x1 ) - ( line1->x1 - line2->x1 ) * ( line2->y2 - line2->y1 ) ) / denominator;
	float s = ( ( line1->y1 - line2->y1 ) * ( line1->x2 - line1->x1 ) - ( line1->x1 - line2->x1 ) * ( line1->y2 - line1->y1 ) ) / denominator;
	
	if( line2->y1 < line2->y2 )
	{
		if( (s >= 0.0f  && s < 1.0f) && ( r >= 0.0f && r <= 1.0f ) )
		{
			return 1;
		}
	}
	else
	{
		if( (s > 0.0f  && s <= 1.0f) && ( r >= 0.0f && r <= 1.0f ) )
		{
			return 1;
		}
	}
	
	return 0;
}
