/***************************************************************************
 * collison.cpp  -  internal collision functions
 *
 * Copyright (C) 2005 - 2009 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 "../level/level.h"
#include "../player/player.h"
#include "../video/gl_surface.h"
#include "../core/sprite_manager.h"
// for binary_function and bind2nd
#include <functional>

namespace SMC
{

/* *** *** *** *** *** *** *** cObjectCollisionType *** *** *** *** *** *** *** *** *** *** */

cObjectCollisionType :: cObjectCollisionType( void )
: cObject_Manager<cObjectCollision>()
{

}

cObjectCollisionType :: ~cObjectCollisionType( void )
{
	Delete_All();
}

void cObjectCollisionType :: Add( cObjectCollision *obj )
{
	if( !obj )
	{
		return;
	}

	cObject_Manager<cObjectCollision>::Add( obj );
}

// check if sprite
struct check_if_sprite : public std::binary_function<cObjectCollision *, const cSprite *, bool>
{
	bool operator()( const cObjectCollision *col, const cSprite *sprite ) const
	{
		return col->obj == sprite;
	}
};
bool cObjectCollisionType :: Is_Included( const cSprite *obj )
{
	return std::find_if( objects.begin(), objects.end(), std::bind2nd( check_if_sprite(), obj ) ) != objects.end();
}

// check if array type
struct check_if_sprite_array : public std::binary_function<cObjectCollision *, ArrayType, bool>
{
	bool operator()( const cObjectCollision *col, ArrayType type ) const
	{
		return col->obj->m_sprite_array == type;
	}
};
bool cObjectCollisionType :: Is_Included( const ArrayType type )
{
	return std::find_if( objects.begin(), objects.end(), std::bind2nd( check_if_sprite_array(), type ) ) != objects.end();
}

// check if sprite type
struct check_if_sprite_type : public std::binary_function<cObjectCollision *, SpriteType, bool>
{
	bool operator()( const cObjectCollision *col, SpriteType type ) const
	{
		return col->obj->m_type == type;
	}
};
bool cObjectCollisionType :: Is_Included( const SpriteType type )
{
	return std::find_if( objects.begin(), objects.end(), std::bind2nd( check_if_sprite_type(), type ) ) != objects.end();
}

// check if validation type
struct check_if_valid_type : public std::binary_function<cObjectCollision *, Col_Valid_Type, bool>
{
	bool operator()( const cObjectCollision *col, Col_Valid_Type type ) const
	{
		return col->valid_type == type;
	}
};
bool cObjectCollisionType :: Is_Included( const Col_Valid_Type type )
{
	return std::find_if( objects.begin(), objects.end(), std::bind2nd( check_if_valid_type(), type ) ) != objects.end();
}

cObjectCollision *cObjectCollisionType :: Find_First( const ArrayType type )
{
	return *std::find_if( objects.begin(), objects.end(), std::bind2nd( check_if_sprite_array(), type ) );
}

cObjectCollision *cObjectCollisionType :: Find_First( const SpriteType type )
{
	return *std::find_if( objects.begin(), objects.end(), std::bind2nd( check_if_sprite_type(), type ) );
}

/* *** *** *** *** *** *** *** cObjectCollision *** *** *** *** *** *** *** *** *** *** */

cObjectCollision :: cObjectCollision( void )
{
	valid_type = COL_VTYPE_NOT_VALID;
	received = 0;
	m_array = ARRAY_UNDEFINED;
	obj = NULL;
	number = 0;
	direction = DIR_UNDEFINED;
}

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

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

GL_rect cObjectCollision :: Get_Collision_Object_Rect( void )
{
	// player
	if( m_array == ARRAY_PLAYER )
	{
		return pActive_Player->m_col_rect;
	}

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

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

ObjectDirection Get_Collision_Direction( const cSprite *base, const cSprite *col )
{
	// if valid moving sprite try the velocity based detection
	if( base->m_sprite_array == ARRAY_ENEMY || base->m_sprite_array == ARRAY_ACTIVE || base->m_sprite_array == ARRAY_PLAYER )
	{
		const cMovingSprite *moving_base = static_cast<const cMovingSprite *>(base);

		// top
		if( moving_base->m_vely < 0.0f )
		{
			if( Is_Collision_Top( base->m_col_rect, col->m_col_rect ) )
			{
				return DIR_TOP;
			}

			if( moving_base->m_velx < 0.0f )
			{
				return DIR_LEFT;
			}
			if( moving_base->m_velx > 0.0f )
			{
				return DIR_RIGHT;
			}
		}
		// bottom
		else if( moving_base->m_vely > 0.0f )
		{
			if( Is_Collision_Bottom( base->m_col_rect, col->m_col_rect ) )
			{
				return DIR_BOTTOM;
			}

			if( moving_base->m_velx < 0.0f )
			{
				return DIR_LEFT;
			}
			if( moving_base->m_velx > 0.0f )
			{
				return DIR_RIGHT;
			}
		}
		// left
		if( moving_base->m_velx < 0.0f )
		{
			if( Is_Collision_Left( base->m_col_rect, col->m_col_rect ) )
			{
				return DIR_LEFT;
			}

			if( moving_base->m_vely < 0.0f )
			{
				return DIR_TOP;
			}
			if( moving_base->m_vely > 0.0f )
			{
				return DIR_BOTTOM;
			}
		}
		// right
		else if( moving_base->m_velx > 0.0f )
		{
			if( Is_Collision_Right( base->m_col_rect, col->m_col_rect ) )
			{
				return DIR_RIGHT;
			}

			if( moving_base->m_vely < 0.0f )
			{
				return DIR_TOP;
			}
			if( moving_base->m_vely > 0.0f )
			{
				return DIR_BOTTOM;
			}
		}
	}

	// ## detection without velocity

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

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

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

	// difference between points
	float diff_x = point_col.m_x - point_base.m_x;
	float diff_y = point_col.m_y - point_base.m_y;

	// more horizontal
	if( ( diff_x > 0.0f && ( diff_x > diff_y ) ) || ( diff_x < 0.0f && ( diff_x < diff_y ) ) )
	{
		// if col object point left
		if( diff_x > 0.0f )
		{
			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.0f )
		{
			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.m_y + 1 >= col_rect.m_y + col_rect.m_h && base_rect.m_x < col_rect.m_x + col_rect.m_w && base_rect.m_x + base_rect.m_w > col_rect.m_x )
	{
		return 1;
	}

	return 0;
}

bool Is_Collision_Bottom( const GL_rect &base_rect, const GL_rect &col_rect )
{
	if( base_rect.m_y + base_rect.m_h - 1 <= col_rect.m_y && base_rect.m_x < col_rect.m_x + col_rect.m_w && base_rect.m_x + base_rect.m_w > col_rect.m_x )
	{
		return 1;
	}

	return 0;
}

bool Is_Collision_Left( const GL_rect &base_rect, const GL_rect &col_rect )
{
	if( base_rect.m_x + 1 >= col_rect.m_x + col_rect.m_w && base_rect.m_y < col_rect.m_y + col_rect.m_h && base_rect.m_y + base_rect.m_h > col_rect.m_y )
	{
		return 1;
	}

	return 0;
}

bool Is_Collision_Right( const GL_rect &base_rect, const GL_rect &col_rect )
{
	if( base_rect.m_x + base_rect.m_w - 1 <= col_rect.m_x && base_rect.m_y < col_rect.m_y + col_rect.m_h && base_rect.m_y + base_rect.m_h > col_rect.m_y )
	{
		return 1;
	}

	return 0;
}

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

	if( b.m_y + b.m_h < a.y )
	{
		return 0;
	}
	if( b.m_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->m_w + a->m_h ) / 4 );	// same as / 2) / 2;
	float r2 = ( ( b->m_w + b->m_h ) / 4 );

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

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

/* *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** */

} // namespace SMC
