/*  $Id: outline_provider_bitmap_generic.cpp,v 1.5 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/Display/Collision/outline_accuracy.h"
#include "API/Display/Collision/outline_circle.h"
#include "API/Display/pixel_buffer.h"
#include "API/Display/pixel_format.h"
#include "outline_provider_bitmap_generic.h"

CL_OutlineProviderBitmap_Generic::CL_OutlineProviderBitmap_Generic(
	CL_PixelBuffer *pbuf,
	int alpha_limit )
:
	pb(pbuf),
	alpha_limit(alpha_limit),
	consecutive_left_turns(0),
	consecutive_right_turns(0),
	last_dir(DIR_RIGHT),
	last_point(0.0f,0.0f)
{
	width = pb->get_width();
	height = pb->get_height();

	if( pbuf->get_format() != CL_PixelFormat::rgba8888 )
	{
		// the image contains no alpha - add only a rectangle

		CL_Contour contour;
		
		contour.points.push_back( CL_Pointf(0.0f,0.0f) );
		contour.points.push_back( CL_Pointf((float)width,0.0f) );
		contour.points.push_back( CL_Pointf((float)width,(float)height) );
		contour.points.push_back( CL_Pointf(0.0f,(float)height) );
		contour.points.push_back( CL_Pointf(0.0f,0.0f) );

//		center(contour.points);

		contours.push_back(contour);

		return;
	}


	pb->lock();
	
	data = static_cast<unsigned char *>(pb->get_data());
	pitch = pb->get_pitch();
	bpp = pb->get_format().get_depth()/8;
	
	CL_Pointf start_point(find_next_contour_start(0,0));
	
	int pixels_tested = 0;
		
	while( start_point.x != -1.0f && start_point.y != -1.0f )
	{
		CL_Contour contour;
		
		contour.points.push_back(start_point);

		// initialize outline finder algorithm (get_next_point)
		last_point = start_point; 
		last_dir = DIR_RIGHT;
		consecutive_left_turns = 0;
		consecutive_right_turns = 0;
			
		while( true )
		{
			get_next_point(contour.points);
			
			if( contour.points.front().x == contour.points.back().x
				&& contour.points.front().y == contour.points.back().y )
			{
				if( pixels_tested > 3 )
				{
					break; // line loop closed
				}
			}

			if( pixels_tested % 5 )
			{
				// The outline algo starts looping when on a
				// "discontinuity" in a one-pixel-wide line. This will
				// break that loop.

				CL_Pointf &point = contour.points.back();

				if( point_in_outline((unsigned int)point.x, (unsigned int)point.y) )
					break;
			}
			pixels_tested++;
		}
		
		// add contour to the countours vector
		contours.push_back(contour);

		int next_search_start_x = (int)start_point.x + 1;
		int next_search_start_y = (int)start_point.y;
		if( next_search_start_x >= width )
		{
			next_search_start_x = 0;
			next_search_start_y++;
			if( next_search_start_y >= height )
				break;
		}

		start_point = find_next_contour_start(
			next_search_start_x,
			next_search_start_y);

		pixels_tested = 0;
	}
	
	pb->unlock();

	// transform points so that (0,0) is at (width/2, height/2)
/*	std::vector<CL_Contour>::iterator it;
	for( it = contours.begin(); it != contours.end(); ++it )
	{
		center((*it).points);
	}
*/
}

CL_OutlineProviderBitmap_Generic::~CL_OutlineProviderBitmap_Generic()
{
}

CL_Pointf CL_OutlineProviderBitmap_Generic::find_next_contour_start(unsigned int sx, unsigned int sy)
{
	bool skip_opaque = false;

	for( int y=0; y < height; ++y )
	{
		for( int x=0; x < width; ++x )
		{
			if( skip_opaque )
			{
				// skip untill encountering an transparent pixel
				if( data[y*pitch+x*bpp] < alpha_limit )
				{
					skip_opaque = false;
				}
				continue;
			}

			if( data[y*pitch+x*bpp] > alpha_limit ) // opaque
			{
				if( point_in_outline(x,y) )
					skip_opaque = true;

				if( !skip_opaque )
					return CL_Pointf((float)x,(float)y);
			}
		}
	}
	
	return CL_Pointf(-1.0f,-1.0f);
}



void CL_OutlineProviderBitmap_Generic::get_next_point(std::vector<CL_Pointf> &points)
{
	/* 	
	 1. After three consecutive turns in the same direction, continue straight ahead

	 2. turn left if (alpha > limit) 

	 3. turn right if (alpha < limit) or outside image
    */

	int x = (int)last_point.x;
	int y = (int)last_point.y;

	bool outside_image = (x<0 || x>width || y<0 || y>height);

	bool turn_left = outside_image ? false : (data[y*pitch + x*bpp] > alpha_limit);

	if( turn_left )
	{
		consecutive_left_turns++;
		consecutive_right_turns = 0;
	}
	else
	{
		consecutive_right_turns++;
		consecutive_left_turns = 0;
	}

	if( consecutive_left_turns == 3 || consecutive_right_turns == 3 )
	{
		switch (last_dir)
		{
		 case DIR_UP: last_dir = DIR_UP; y++; break;
		 case DIR_DOWN: last_dir = DIR_DOWN; y--; break;
		 case DIR_LEFT: last_dir = DIR_LEFT; x--; break;
		 case DIR_RIGHT: last_dir = DIR_RIGHT; x++; break;
		}

		last_point.x = (float)x;
		last_point.y = (float)y;

		outside_image = (x<0 || x>width || y<0 || y>height);

		if( !outside_image )
			if( data[y*pitch+x*bpp] > alpha_limit )
				points.push_back(CL_Pointf((float)x,(float)y));

		consecutive_left_turns = consecutive_right_turns = 0;

		return;
	}
	
	if( turn_left )
	{
		switch (last_dir)
		{
		 case DIR_UP: last_dir = DIR_LEFT; x--; break;
		 case DIR_DOWN: last_dir = DIR_RIGHT; x++; break;
		 case DIR_LEFT: last_dir = DIR_DOWN; y--; break;
		 case DIR_RIGHT: last_dir = DIR_UP; y++; break;
		}
	}
	else
	{
		switch (last_dir)
		{
		 case DIR_UP: last_dir = DIR_RIGHT; x++; break;
		 case DIR_DOWN: last_dir = DIR_LEFT; x--; break;
		 case DIR_LEFT: last_dir = DIR_UP; y++; break;
		 case DIR_RIGHT: last_dir = DIR_DOWN; y--; break;
		}
	}

	outside_image = (x<0 || x>width || y<0 || y>height);

	if( !outside_image )
		if( data[y*pitch+x*bpp] > alpha_limit )
			points.push_back(CL_Pointf((float)x,(float)y));
	
	last_point.x = (float)x;
	last_point.y = (float)y;
}

void CL_OutlineProviderBitmap_Generic::center(std::vector<CL_Pointf> &points)
{
	std::vector<CL_Pointf>::iterator it;
	for( it = points.begin(); it != points.end(); ++it )
	{
		(*it).x -= width/2.0f;
		(*it).y -= height/2.0f;
	}
}

bool CL_OutlineProviderBitmap_Generic::point_in_outline(unsigned int x, unsigned int y)
{
	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 )
		{
			if( (*ita).x == x && (*ita).y == y ) 
			{
				return true;
			}
		}
	}

	return false;
}
