/*  $Id: collision_outline_generic.cpp,v 1.10 2004/05/08 21:20:19 harry Exp $
**
**  ClanLib Game SDK
**  Copyright (C) 2003  The ClanLib Team
**  For a total list of contributers see the file CREDITS.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This library 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
**  Lesser General Public License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
**
*/

#include "Display/display_precomp.h"
#include "API/Core/IOData/outputsource.h"
#include "API/Core/IOData/outputsource_provider.h"
#include "collision_outline_generic.h"
#include "API/Display/Collision/collision_outline.h"
#include "API/Display/Collision/outline_provider.h"
#include "API/Display/Collision/outline_accuracy.h"
#include "API/Core/Math/line_math.h"
#include "API/Core/Math/cl_vector.h"
#include "API/Core/IOData/outputsource_file.h"

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

/////////////////////////////////////////////////////////////////////////////
// Construction:

CL_CollisionOutline_Generic::CL_CollisionOutline_Generic()
:
	do_obb_test(false),
	do_inside_test(false),
	width(0), height(0),
	angle(0),
	radius(0),
	position(0,0),
	scale_factor(1,1),
	translation_offset(0,0),
	translation_origin(origin_top_left),
	rotation_hotspot(0,0),
	rotation_origin(origin_center),
	collision_info(false),
	collision_info_normals(false)
{
	return;
}

CL_CollisionOutline_Generic::CL_CollisionOutline_Generic(
	CL_OutlineProvider *provider,
	CL_OutlineAccuracy accuracy )
:
	do_obb_test(false),
	do_inside_test(false),
	width(0), height(0),
	angle(0),
	radius(0),
	position(0,0),
	scale_factor(1,1),
	translation_offset(0,0),
	translation_origin(origin_top_left),
	rotation_hotspot(0,0),
	rotation_origin(origin_center),
	collision_info(false)
{
	contours = provider->get_contours();
	width = provider->get_width();
	height = provider->get_height();

	delete provider;

	switch( accuracy )
	{
	 case accuracy_high:
		optimize(3, float(M_PI/7.0f));
		break;
	 case accuracy_medium:
		optimize(3, float(M_PI/6.0f));
		break;
	 case accuracy_low:
		optimize(3, float(M_PI/5.0f));
		break;
	 case accuracy_poor:
		optimize(3, float(M_PI/4.0f));
		break;
	 case accuracy_raw:
		break;
	default:
		break;
	}

	calculate_radius();
	calculate_sub_circles();
}


CL_CollisionOutline_Generic::~CL_CollisionOutline_Generic()
{
}

/////////////////////////////////////////////////////////////////////////////
// Operations:

void CL_CollisionOutline_Generic::set_translation(float x, float y, bool offset_points)
{
	CL_Pointf old_position = position;

	if( !offset_points )
	{
		position.x = x;
		position.y = y;
	}

	CL_Pointf translation;

	if( offset_points )
	{
		translation.x = x;
		translation.y = y;
	}
	else
		translation = (position - old_position);

	std::vector<CL_Contour>::iterator it;
	for( it = contours.begin(); it != contours.end(); ++it )
	{
		std::vector<CL_Pointf>::iterator ita;
		for( ita = (*it).points.begin(); ita != (*it).points.end(); ++ita )
			(*ita) += translation;
	}

	for( it = contours.begin(); it != contours.end(); ++it )
	{
		std::vector<CL_OutlineCircle>::iterator ita;
		for( ita = (*it).sub_circles.begin(); ita != (*it).sub_circles.end(); ++ita )
		{
			(*ita).position += translation;
		}
	}
	
	if( do_obb_test )
		update_obb();
}

void CL_CollisionOutline_Generic::rotate(float add_angle)
{
	angle += add_angle;

	std::vector<CL_Contour>::iterator it;
	for( it = contours.begin(); it != contours.end(); ++it )
	{
		std::vector<CL_Pointf>::iterator ita;
		for( ita = (*it).points.begin(); ita != (*it).points.end(); ++ita )
		{
			(*ita) = (*ita).rotate(position+rotation_hotspot, add_angle);
		}
	}

	for( it = contours.begin(); it != contours.end(); ++it )
	{
		std::vector<CL_OutlineCircle>::iterator ita;
		for( ita = (*it).sub_circles.begin(); ita != (*it).sub_circles.end(); ++ita )
		{
			(*ita).position = (*ita).position.rotate(position+rotation_hotspot, add_angle);
		}
	}

	if( do_obb_test )
		update_obb();
}

void CL_CollisionOutline_Generic::set_angle(float angle)
{
	float rotate_angle = angle - this->angle;
	this->angle = angle;

	std::vector<CL_Contour>::iterator it;
	for( it = contours.begin(); it != contours.end(); ++it )
	{
		std::vector<CL_Pointf>::iterator ita;
		for( ita = (*it).points.begin(); ita != (*it).points.end(); ++ita )
		{
			(*ita) = (*ita).rotate(position+rotation_hotspot, rotate_angle);
		}
	}

	for( it = contours.begin(); it != contours.end(); ++it )
	{
		std::vector<CL_OutlineCircle>::iterator ita;
		for( ita = (*it).sub_circles.begin(); ita != (*it).sub_circles.end(); ++ita )
		{	
			(*ita).position = (*ita).position.rotate(position+rotation_hotspot, rotate_angle);
		}
	}

	if( do_obb_test )
		update_obb();
}

void CL_CollisionOutline_Generic::set_scale(float sx, float sy)
{
	if( scale_factor.x == sx && scale_factor.y == sy )
		return;

	float scale_x = 1.0f + sx - scale_factor.x;
	float scale_y = 1.0f + sy - scale_factor.y;
	
	scale_factor.x = sx;
	scale_factor.y = sy;

	std::vector<CL_Contour>::iterator it;
	for( it = contours.begin(); it != contours.end(); ++it )
	{
		std::vector<CL_Pointf>::iterator ita;
		for( ita = (*it).points.begin(); ita != (*it).points.end(); ++ita )
		{
			(*ita).x = position.x + (((*ita).x-position.x)*scale_x);
			(*ita).y = position.y + (((*ita).y-position.y)*scale_y);
		}
	}
	
	calculate_sub_circles();

	if( do_obb_test )
		update_obb();

	calculate_radius();
}

void CL_CollisionOutline_Generic::update_obb()
{
	//Find the rotated positions of each corner
	obb.points[0] = CL_Pointf(orig_obb.left, orig_obb.top).rotate(CL_Pointf(0.0f,0.0f), angle);
	obb.points[1] = CL_Pointf(orig_obb.right, orig_obb.top).rotate(CL_Pointf(0.0f,0.0f), angle);
	obb.points[2] = CL_Pointf(orig_obb.left, orig_obb.bottom).rotate(CL_Pointf(0.0f,0.0f), angle);
	obb.points[3] = CL_Pointf(orig_obb.right, orig_obb.bottom).rotate(CL_Pointf(0.0f,0.0f), angle);
	
	for( int i=0; i<4; ++i )
	{
		obb.points[i].x *= scale_factor.x;
		obb.points[i].y *= scale_factor.y;

		obb.points[i].x += position.x;
		obb.points[i].y += position.y;
	}
}

void CL_CollisionOutline_Generic::calculate_obb()
{
	orig_obb = CL_Rectf(0.0f,0.0f,0.0f,0.0f);

	float l=0,r=0,u=0,d=0;

	std::vector<CL_Contour>::iterator it;
	for( it = contours.begin(); it != contours.end(); ++it )
	{
		for( unsigned int i=0; i<(*it).points.size()-1; ++i )
		{
			if( (*it).points[i].x < l )
				l = (*it).points[i].x;

			if( (*it).points[i].x > r )
				r = (*it).points[i].x;

			if( (*it).points[i].y < u )
				u = (*it).points[i].y;

			if( (*it).points[i].y > d )
				d = (*it).points[i].y;
		}
	}

	orig_obb.left = l;
	orig_obb.right = r;
	orig_obb.top = u;
	orig_obb.bottom = d;

	obb.points.push_back(CL_Pointf(l,u));
	obb.points.push_back(CL_Pointf(r,u));
	obb.points.push_back(CL_Pointf(l,d));
	obb.points.push_back(CL_Pointf(r,d));
}

void CL_CollisionOutline_Generic::calculate_radius()
{
	radius = 0;

	std::vector<CL_Contour>::iterator it;
	for( it = contours.begin(); it != contours.end(); ++it )
	{
		for( unsigned int i=0; i<(*it).points.size(); ++i )
		{
			float test_radius = sqrt(
				pow2(position.x - (*it).points[i].x) +
				pow2(position.y - (*it).points[i].y) );

			if( test_radius > radius )	
				radius = test_radius;
		}	
	}
}

void CL_CollisionOutline_Generic::calculate_sub_circles()
{	
	unsigned int test_start = 0;

	std::vector<CL_Contour>::iterator it;
	for( it = contours.begin(); it != contours.end(); ++it )
	{
		(*it).sub_circles.clear();
		test_start = 0;

		// sub_radius = average of line lenghts in the contour * 3.5
		float sub_radius = 0.0f;
		for( unsigned int j=0; j<(*it).points.size()-1; ++j )
		{
			sub_radius += ((*it).points[j].distance((*it).points[j+1]));
		}
		sub_radius /= (*it).points.size();
		sub_radius *= 3.5f;

		for( unsigned int i=0; i<(*it).points.size()-1; ++i )
		{
			if( (*it).points[test_start].distance((*it).points[i]) > sub_radius )
			{
				CL_OutlineCircle circle;
				circle.position = CL_LineMath::midpoint( (*it).points[test_start], (*it).points[i] );
				circle.start = test_start;
				circle.end = i;
				
				// expand radius so that all points are inside the circle
				for( int test = circle.start; test < circle.end; ++test )
				{
					float dist = circle.position.distance((*it).points[test]);
	
					if( dist > circle.radius )
						circle.radius = dist;	
				}
			
				(*it).sub_circles.push_back(circle);
				test_start = i;
			}
		}

		// add remaining points into a collision circle
		if( test_start != (*it).points.size()-1 )
		{
			CL_OutlineCircle circle;

			circle.position = CL_LineMath::midpoint( (*it).points[test_start], (*it).points.back() );
			circle.start = test_start;
			circle.end = (*it).points.size()-1; // include also the first point. use pos % size when checking.

			// expand radius so that all points are inside
			for( unsigned int test = test_start; test < (*it).points.size(); test++ )
			{
				int real_index = test % (*it).points.size();
				
				float dist = circle.position.distance((*it).points[real_index]);
				
				if( dist > circle.radius )
					circle.radius = dist+0.01f;
			}

			(*it).sub_circles.push_back(circle);
		}
	}
}

void CL_CollisionOutline_Generic::optimize(unsigned char check_distance, float corner_angle)
{
	unsigned char orig_check_distance = check_distance;

	std::vector<CL_Contour>::iterator it;
	for( it = contours.begin(); it != contours.end(); ++it )
	{
		check_distance = orig_check_distance;

		std::vector<CL_Pointf> &points = (*it).points;

		if( points.empty() ) continue;

		std::vector<CL_Pointf> optimized;

		optimized.push_back(points.front());

		if( static_cast<int>(points.size()) < check_distance )
			check_distance = 1;

		for( unsigned int i=0; i < points.size()-check_distance; ++i )
		{
			int A_index = i-check_distance;
			int B_index = i+check_distance;

			if( A_index < 0 )
			{
				if( points.front() == points.back() )
					A_index += points.size();
				else
					A_index = 0;
			}
			if( B_index > (int)points.size() )
			{
				if( points.front() == points.back() )
					B_index -= points.size();
				else
					B_index = (int)points.size();
			}

			CL_Pointf &A = optimized.back();
			CL_Pointf &B = points[i];
			CL_Pointf &C = points[B_index];

			CL_Vector AB(B.x-A.x, B.y-A.y);
			CL_Vector BC(C.x-B.x, C.y-B.y);

			if( check_distance != 1 && AB.norm() < 2 )
				continue;

			float angle = AB.angle(BC);
			
			if( angle > corner_angle )
			{
				optimized.push_back(points[i]);
			}
		}

		optimized.push_back(points[0]); // complete the loop.

		points = optimized;		
	}
}

void CL_CollisionOutline_Generic::save(const std::string &filename, CL_OutputSourceProvider *provider) const
{
/*	fileformat:

	uint32  type        // file type identifier	
	uint8   version     // file version	

	uint32 num_contours
		uint32 num_points contour 1
			float32 px1
			float32 py1
			float32 px2
			float32 py2
			... contour 1 data ...
		uint32 num_points contour 2
			... contour 2 data ...
		uint32 num_points contour N
			... contour N data ...
*/

/*	if( provider == 0 )
		provider = new CL_OutputSourceProvider::open_source(".");
	else
		provider = _provider->clone();
*/

	CL_OutputSource *output_source = new CL_OutputSource_File(filename); // provider->open_source(filename);

	// file type identifyer
	output_source->write_uint32( 0x03042004 );

	// fileformat version
	output_source->write_uint8(1);
	
	// number of contours
	output_source->write_uint32(contours.size());
	
	std::vector<CL_Contour>::const_iterator it_cont;
	for( it_cont = contours.begin(); it_cont != contours.end(); ++it_cont )
	{
		// number of points in contours
		output_source->write_uint32((*it_cont).points.size());
		
		std::vector<CL_Pointf>::const_iterator it;
		for( it = (*it_cont).points.begin(); it != (*it_cont).points.end(); ++it )
		{
			// x,y of points
			output_source->write_float32((*it).x);
			output_source->write_float32((*it).y);
		}
	}

	output_source->close();

	delete output_source;
}


bool CL_CollisionOutline_Generic::collide( const CL_CollisionOutline &outline )
{
	if( collision_info )
	{
		collision_points.clear();
		collision_normals.clear();
	}

	// bounding circle test.
	float dist = position.distance(outline.get_translation());
	
	if( dist > (radius + outline.get_radius() ))
		return false;

	// object aligned bounding box test.
	
	if( do_obb_test || outline.get_obb_test() )
	{
		const CL_Contour &obb2 = outline.get_object_bounding_box();
	
		if( !obb_overlap(obb2) )
			return false;
	}

	// collision sub circle test

	std::vector<CL_Contour>::const_iterator it_contours, it_contours2;
	for( it_contours = contours.begin(); it_contours != contours.end(); ++it_contours )
	{
		for( it_contours2 = outline.get_contours().begin();
			 it_contours2 != outline.get_contours().end();
			 ++it_contours2 )
		{
			if( contours_collide( (*it_contours), (*it_contours2) ) )
			{
				if( collision_info == false ) 
					return true; // don't return info about all line intersections
			}
		}
	}

	// collisions found
	if( collision_info && !collision_points.empty() )
		return true;

	// test if one outline is completely inside the other

	if( do_inside_test || outline.get_inside_test() )
	{
		std::vector<CL_Contour>::const_iterator ita, itb;

		for( ita = contours.begin(); ita != contours.end(); ++ita )
		{
			for( itb = outline.get_contours().begin(); itb != outline.get_contours().end(); ++itb )
			{
				if( point_inside_contour((*ita).points[0], (*itb)) ||
					point_inside_contour((*itb).points[0], (*ita)) )
				{
					return true;
				}
			}
		}
	}
	
	return false;
}


bool CL_CollisionOutline_Generic::point_inside( const CL_Pointf &point ) const
{
	float dist = position.distance(point);

	if( dist > radius )
	{	
		return false;
	}

	std::vector<CL_Contour>::const_iterator it;	
	for( it = contours.begin(); it != contours.end(); ++it )
	{
		if( point_inside_contour(point, (*it)) )
		{
			return true;
		}
	}
	
	return false;
}

/////////////////////////////////////////////////////////////////////////////
// CL_CollisionOutline_Generic Implementation:

bool CL_CollisionOutline_Generic::point_inside_contour( const CL_Pointf &point, const CL_Contour &contour ) const
{
	float lineX[4];
	lineX[0] = point.x;
	lineX[1] = point.y+0.001f;
	lineX[2] = point.x+9999.0f; // contour.get_radius()*4;
	lineX[3] = point.y+0.001f;

	// collide the line with the outline.
	int num_intersections_x = 0;

	const std::vector<CL_Pointf> &points = contour.points;

	std::vector<CL_OutlineCircle>::const_iterator it;
	for( it = contour.sub_circles.begin();
		 it != contour.sub_circles.end();
		 ++it )
	{
		const CL_OutlineCircle &circle = (*it);

		float dist = fabs(circle.position.y - point.y);

		if( dist <= circle.radius )
		{
			// test each line segment inside the circle
		
			for( int i=circle.start; i < circle.end; ++i )
			{
				float line2[4];
				line2[0] = points[i].x;
				line2[1] = points[i].y;
				line2[2] = points[i+1].x;
				line2[3] = points[i+1].y;

				// avoid vertices
				if( line2[1] == lineX[1] )
				{
					line2[1] += 0.001f;
					line2[3] += 0.001f;
				}

				if( CL_LineMath::intersects(lineX, line2, false) )
				{
					num_intersections_x++;
				}
			}
		}
	}

	if( num_intersections_x % 2 )
	{
		return true;
	}
	
	return false;
}

inline bool CL_CollisionOutline_Generic::line_bounding_box_overlap( const std::vector<CL_Pointf> &rect1, const std::vector<CL_Pointf> &rect2, int i, int j ) const
{
	float left   = cl_min(rect1[i].x, rect1[i+1].x);
	float right  = cl_max(rect1[i].x, rect1[i+1].x);
	float top    = cl_min(rect1[i].y, rect1[i+1].y);
	float bottom = cl_max(rect1[i].y, rect1[i+1].y);	

	float r_left   = cl_min(rect2[j].x, rect2[j+1].x);
	float r_right  = cl_max(rect2[j].x, rect2[j+1].x);
	float r_top    = cl_min(rect2[j].y, rect2[j+1].y);
	float r_bottom = cl_max(rect2[j].y, rect2[j+1].y);

	return (r_left <= right && r_right >= left && r_top <= bottom && r_bottom >= top);
}

// object bounding box (obb) overlap test
bool CL_CollisionOutline_Generic::obb_overlap( const CL_Contour &other_obb )
{
	if( contours_collide(obb, other_obb, false) )
		return true;
	
	return false;
}


bool CL_CollisionOutline_Generic::contours_collide(const CL_Contour &contour1, const CL_Contour &contour2, bool do_subcirle_test)
{
	std::vector<CL_OutlineCircle>::const_iterator it_oc1, it_oc2;

	for( it_oc1 = contour1.sub_circles.begin(); it_oc1 != contour1.sub_circles.end(); ++it_oc1 )
	{
		for( it_oc2 = contour2.sub_circles.begin(); it_oc2 != contour2.sub_circles.end(); ++it_oc2 )
		{
			if( do_subcirle_test ? (*it_oc1).collide(*it_oc2) : true ) // outline circles collide
			{
				// test each line segment inside the colliding circles

				const std::vector<CL_Pointf> &points1 = contour1.points;
				const std::vector<CL_Pointf> &points2 = contour2.points;
				
				int num_points1 = points1.size()-1;
				int num_points2 = points2.size()-1;
				
				for( int counter_i=(*it_oc1).start; counter_i < (*it_oc1).end; ++counter_i )
				{
					int i = counter_i % num_points1;
					
					for( int counter_j=(*it_oc2).start; counter_j < (*it_oc2).end; ++counter_j )
					{
						int j = counter_j % num_points2;
						
						if( line_bounding_box_overlap(points1, points2, i, j) )
						{
							float line1[4];
							line1[0] = points1[i].x;
							line1[1] = points1[i].y;
							line1[2] = points1[i+1].x;
							line1[3] = points1[i+1].y;
							
							float line2[4];
							line2[0] = points2[j].x;
							line2[1] = points2[j].y;
							line2[2] = points2[j+1].x;
							line2[3] = points2[j+1].y;

							if( CL_LineMath::intersects( line1, line2 ) )
							{
								if( collision_info )
								{
									collision_points.push_back(CL_LineMath::get_intersection(line1,line2));

									if( collision_info_normals )
									{
										CL_Pointf normal;
										normal.x = -1 * (line2[3]-line2[1]);
										normal.y = (line2[2]-line2[0]);

										float len = fabs(sqrt(normal.x*normal.x + normal.y*normal.y));
										normal.x /= len;
										normal.y /= len;

										collision_normals.push_back(normal);
									}
								}
								else
									return true;
							}
						}
					}
				}
			}
		}
	}

	return false;
}
