/***************************************************************************
 * layer.cpp  -  Overworld Layer class
 *
 * Copyright (C) 2003 - 2008 Florian Richter
 ***************************************************************************/
/*
   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 "../overworld/world_layer.h"
#include "../video/renderer.h"
#include "../core/game_core.h"
#include "../core/camera.h"
#include "../overworld/overworld.h"
#include "../core/sprite_manager.h"
#include "../core/i18n.h"


/* *** *** *** *** *** *** *** *** cLayer_Line_Point *** *** *** *** *** *** *** *** *** */

cLayer_Line_Point :: cLayer_Line_Point( SpriteType ntype )
: cSprite()
{
	sprite_array = ARRAY_PASSIVE;
	type = ntype;
	massivetype = MASS_PASSIVE;
	posz = 0.087f;

	linked_point = NULL;

	if( type == TYPE_OW_LINE_START )
	{
		posz += 0.001f;
		color = orange;
		name = _("Line Start Point");
	}
	else
	{
		color = red;
		name = _("Line End Point");
	}

	rect.w = 4;
	rect.h = 4;
	col_rect.w = rect.w;
	col_rect.h = rect.h;
	start_rect.w = rect.w;
	start_rect.h = rect.h;

	Update_Position_Rect();

	player_range = 0;
}

cLayer_Line_Point :: ~cLayer_Line_Point( void )
{
	Destroy();

	// remove linked point
	if( linked_point )
	{
		linked_point->linked_point = NULL;
	}
}

void cLayer_Line_Point :: Draw( cSurfaceRequest *request /* = NULL */ )
{
	if( destroy || !pOverworld_Manager->draw_layer )
	{
		return;
	}

	// point rect
	pVideo->Draw_Rect( col_rect.x - pActive_Camera->x, col_rect.y - pActive_Camera->y, col_rect.w, col_rect.h, posz, &color );
}

void cLayer_Line_Point :: Destroy( void )
{
	if( destroy )
	{
		return;
	}

	if( type == TYPE_OW_LINE_START )
	{
		pActive_Overworld->pLayer->Delete( static_cast<cLayer_Line_Point_Start *>(this), 0 );
	}

	cSprite::Destroy();

	// destroy linked point
	if( linked_point && !linked_point->destroy )
	{
		linked_point->Destroy();
	}
}

float cLayer_Line_Point :: Get_Line_Pos_X( void )
{
	return posx + ( col_rect.w * 0.5f );
}

float cLayer_Line_Point :: Get_Line_Pos_Y( void )
{
	return posy + ( col_rect.h * 0.5f );
}


/* *** *** *** *** *** *** *** *** cLayer_Line_Point_Start *** *** *** *** *** *** *** *** *** */

cLayer_Line_Point_Start :: cLayer_Line_Point_Start( void )
: cLayer_Line_Point( TYPE_OW_LINE_START )
{
	cLayer_Line_Point_Start::Init();
}

cLayer_Line_Point_Start :: cLayer_Line_Point_Start( CEGUI::XMLAttributes &attributes )
: cLayer_Line_Point( TYPE_OW_LINE_START )
{
	cLayer_Line_Point_Start::Init();
	cLayer_Line_Point_Start::Create_from_Stream( attributes );
}

cLayer_Line_Point_Start :: ~cLayer_Line_Point_Start( void )
{
	Destroy();

	// remove linked point
	if( linked_point )
	{
		linked_point->linked_point = NULL;
	}
}

void cLayer_Line_Point_Start :: Init( void )
{
	anim_type = 0;
	origin = 0;

	// create end point
	linked_point = new cLayer_Line_Point( TYPE_OW_LINE_END );
	linked_point->linked_point = this;
}

cLayer_Line_Point_Start *cLayer_Line_Point_Start :: Copy( void )
{
	// create layer line
	cLayer_Line_Point_Start *layer_line = new cLayer_Line_Point_Start();

	// start position
	layer_line->Set_Pos( posx, posy, 1 );
	// end position
	layer_line->linked_point->Set_Pos( posx + 20, posy, 1 );

	// origin
	layer_line->origin = origin;

	return layer_line;
}

void cLayer_Line_Point_Start :: Create_from_Stream( CEGUI::XMLAttributes &attributes )
{
	// Start
	Set_Pos( static_cast<float>(attributes.getValueAsInteger( "X1" )) - 2, static_cast<float>(attributes.getValueAsInteger( "Y1" )) - 2, 1 );
	// End
	linked_point->Set_Pos( static_cast<float>(attributes.getValueAsInteger( "X2" )) - 2, static_cast<float>(attributes.getValueAsInteger( "Y2" )) - 2, 1 );
	// origin
	origin = attributes.getValueAsInteger( "origin" );
}

void cLayer_Line_Point_Start :: Draw( cSurfaceRequest *request /* = NULL */ )
{
	// not a valid draw
	if( destroy || linked_point->destroy || !pOverworld_Manager->draw_layer )
	{
		return;
	}

	// create request
	cLineRequest *line_request = new cLineRequest();

	// drawing color
	Color color = darkgreen;

	// if active
	if( pOverworld_Player->current_line >= 0 && pActive_Overworld->pLayer->objects[pOverworld_Player->current_line] == this )
	{
		color = lightblue;
	}

	pVideo->Draw_Line( Get_Line_Pos_X() - pActive_Camera->x, Get_Line_Pos_Y() - pActive_Camera->y, linked_point->Get_Line_Pos_X() - pActive_Camera->x, linked_point->Get_Line_Pos_Y() - pActive_Camera->y, 0.085f, &color, line_request );
	line_request->line_width = 6;

	// add request
	pRenderer->Add( line_request );

	// draw point rect
	cLayer_Line_Point::Draw( request );
}

GL_line cLayer_Line_Point_Start :: Get_Line( void )
{
	return GL_line( posx + ( col_rect.w * 0.5f ), posy + ( col_rect.h * 0.5f ), linked_point->posx + ( linked_point->col_rect.w * 0.5f ), linked_point->posy + ( linked_point->col_rect.h * 0.5f ) );
}

cWaypoint *cLayer_Line_Point_Start :: Get_End_Waypoint( void )
{
	// get waypoint number
	int wp_num = pActive_Overworld->Get_Waypoint_Collision( &linked_point->col_rect );

	// no waypoint collision
	if( wp_num < 0 )
	{
		cLayer_Line_Point_Start *line_col = pActive_Overworld->pLayer->Get_Line_Collision_Start( &linked_point->col_rect );

		// line collision
		if( line_col )
		{
			// follow line
			return line_col->Get_End_Waypoint();
		}
	}

	// return Waypoint
	return pActive_Overworld->Get_Waypoint( wp_num );
}

void cLayer_Line_Point_Start :: Editor_Activate( void )
{
	CEGUI::WindowManager &wmgr = CEGUI::WindowManager::getSingleton();

	// origin
	CEGUI::Editbox *editbox = static_cast<CEGUI::Editbox *>(wmgr.createWindow( "TaharezLook/Editbox", "layer_line_origin" ));
	Editor_Add( UTF8_("Waypoint origin"), UTF8_("Waypoint origin"), editbox, 100 );

	editbox->setText( int_to_string( origin ) );
	editbox->subscribeEvent( CEGUI::Editbox::EventKeyUp, CEGUI::Event::Subscriber( &cLayer_Line_Point_Start::Editor_Origin_Key, this ) );

	// init
	Editor_Init();
}

bool cLayer_Line_Point_Start :: Editor_Origin_Key( const CEGUI::EventArgs &event )
{
	const CEGUI::WindowEventArgs &windowEventArgs = static_cast<const CEGUI::WindowEventArgs&>( event );
	string str_text = static_cast<CEGUI::Editbox *>( windowEventArgs.window )->getText().c_str();

	origin = string_to_int( str_text );

	return 1;
}

/* *** *** *** *** *** *** *** *** Line Collision *** *** *** *** *** *** *** *** *** */

cLine_collision :: cLine_collision( void )
{
	line = NULL;
	line_number = -2;
	difference = 0;
}

/* *** *** *** *** *** *** *** *** Near Line Collision *** *** *** *** *** *** *** *** *** */

cNearLine_collision :: cNearLine_collision( void )
{
	line_number = -2;
	start = 0;
}

/* *** *** *** *** *** *** *** *** Contact Collision *** *** *** *** *** *** *** *** *** */

void cContact_collision :: clear( void )
{
	contact = 0;
	line_hor = cLine_collision();
	line_ver = cLine_collision();
}

int cContact_collision :: Get_Best_Line( ObjectDirection dir )
{
	// favor vertical
	if( dir == DIR_LEFT || dir == DIR_RIGHT )
	{
		if( line_ver.line_number >= 0 && line_ver.difference < line_hor.difference )
		{
			return line_ver.line_number;
		}
		else if( line_hor.line_number >= 0 )
		{
			return line_hor.line_number;
		}
		else
		{
			return line_ver.line_number;
		}
	}
	// favor horizontal
	else if( dir == DIR_UP || dir == DIR_DOWN )
	{
		if( line_hor.line_number >= 0 && line_hor.difference < line_ver.difference )
		{
			return line_hor.line_number;
		}
		else if( line_ver.line_number >= 0 )
		{
			return line_ver.line_number;
		}
		else
		{
			return line_hor.line_number;
		}
	}

	return -2;
}

/* *** *** *** *** *** *** *** *** Layer *** *** *** *** *** *** *** *** *** */

cLayer :: cLayer( cOverworld *origin )
{
	n_origin = origin;
}

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

void cLayer :: Add( cLayer_Line_Point_Start *line_point )
{
	for( LayerLineList::iterator itr = objects.begin(), itr_end = objects.end(); itr != itr_end; ++itr )
	{
		// get pointer
		cLayer_Line_Point_Start *layer_line = (*itr);

		// if already in layer
		if( layer_line == line_point )
		{
			return;
		}
	}

	cObject_Manager<cLayer_Line_Point_Start>::Add( line_point );

	// check if in sprite manager
	if( n_origin->sprite_manager->Get_Array_Num( line_point ) == -1 )
	{
		// add start point
		n_origin->sprite_manager->Add( line_point );
	}
	// check if in sprite manager
	if( n_origin->sprite_manager->Get_Array_Num( line_point->linked_point ) == -1 )
	{
		// add end point
		n_origin->sprite_manager->Add( line_point->linked_point );
	}
}

void cLayer :: Load( string filename )
{
	Delete_All();

	try
	{
		// parse layer
		CEGUI::System::getSingleton().getXMLParser()->parseXMLFile( *this, filename.c_str(), DATA_DIR "/" GAME_SCHEMA_DIR "/World/Lines.xsd", "" );
	}
	// catch CEGUI Exceptions
	catch( CEGUI::Exception &ex )
	{
		printf( "Loading Line Layer %s CEGUI Exception %s\n", filename.c_str(), ex.getMessage().c_str() );
		debugdisplay->Set_Text( _("Line Layer Loading failed : ") + (string)ex.getMessage().c_str() );
	}
}

bool cLayer :: Save( string filename )
{
	ofstream file( filename.c_str(), ios::out | ios::trunc );

	if( !file )
	{
		debugdisplay->Set_Text( _("Couldn't save world layer ") + filename );
		return 0;
	}

	// xml info
	file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
	// begin layer
	file << "<layer>" << std::endl;

	// lines
	for( LayerLineList::iterator itr = objects.begin(), itr_end = objects.end(); itr != itr_end; ++itr )
	{
		cLayer_Line_Point_Start *line = (*itr);

		// begin line
		file << "\t<line>" << std::endl;
			// start
			file << "\t\t<Property name=\"X1\" value=\"" << static_cast<int>(line->Get_Line_Pos_X()) << "\" />" << std::endl;
			file << "\t\t<Property name=\"Y1\" value=\"" << static_cast<int>(line->Get_Line_Pos_Y()) << "\" />" << std::endl;
			// end
			file << "\t\t<Property name=\"X2\" value=\"" << static_cast<int>(line->linked_point->Get_Line_Pos_X()) << "\" />" << std::endl;
			file << "\t\t<Property name=\"Y2\" value=\"" << static_cast<int>(line->linked_point->Get_Line_Pos_Y()) << "\" />" << std::endl;
			// origin
			file << "\t\t<Property name=\"origin\" value=\"" << line->origin << "\" />" << std::endl;
		// end line
		file << "\t</line>" << std::endl;
	}

	// end layer
	file << "</layer>" << std::endl;
	file.close();

	return 1;
}

void cLayer :: Delete_All( void )
{
	// only clear array
	objects.clear();
}

cLayer_Line_Point_Start *cLayer :: Get_Line_Collision_Start( GL_rect *line_rect )
{
	for( LayerLineList::iterator itr = objects.begin(), itr_end = objects.end(); itr != itr_end; ++itr )
	{
		// get pointer
		cLayer_Line_Point_Start *layer_line = (*itr);

		// check line 1
		if( Col_Box( line_rect, &layer_line->col_rect ) )
		{
			return layer_line;
		}
	}

	return NULL;
}

cLine_collision cLayer :: Get_Line_Collision_Direction( float x, float y, ObjectDirection dir, float dir_size /* = 10 */, unsigned int check_size /* = 10 */ )
{
	if( dir == DIR_UP )
	{
		y -= dir_size;
	}
	else if( dir == DIR_DOWN )
	{
		y += dir_size;
	}
	else if( dir == DIR_RIGHT )
	{
		x += dir_size;
	}
	else if( dir == DIR_LEFT )
	{
		x -= dir_size;
	}

	if( dir == DIR_LEFT || dir == DIR_RIGHT )
	{
		return n_origin->pLayer->Get_Nearest( x, y, DIR_VERTICAL, check_size );
	}
	else if( dir == DIR_UP || dir == DIR_DOWN )
	{
		return n_origin->pLayer->Get_Nearest( x, y, DIR_HORIZONTAL, check_size );
	}

	// invalid direction
	return cLine_collision();
}

cLine_collision cLayer :: Get_Nearest( float x, float y, ObjectDirection dir /* = DIR_HORIZONTAL */, unsigned int check_size /* = 15 */, int only_origin_id /* = -1 */ )
{
	for( LayerLineList::iterator itr = objects.begin(), itr_end = objects.end(); itr != itr_end; ++itr )
	{
		// get pointer
		cLayer_Line_Point_Start *layer_line = (*itr);

		// line is not from waypoint
		if( only_origin_id >= 0 && only_origin_id != layer_line->origin )
		{
			continue;
		}

		cLine_collision col = Get_Nearest_Line( layer_line, x, y, dir, check_size );

		// found
		if( col.line )
		{
			return col;
		}
	}

	// none found
	return cLine_collision();
}

cLine_collision cLayer :: Get_Nearest_Line( cLayer_Line_Point_Start *map_layer_line, float x, float y, ObjectDirection dir /* = DIR_HORIZONTAL */, unsigned int check_size /* = 15  */ )
{
	GL_line line_1, line_2;

	// create map line
	GL_line map_line = map_layer_line->Get_Line();

	// check into both directions from inside
	for( float csize = 0; csize < check_size; csize++ )
	{
		line_1.x1 = x;
		line_1.y1 = y;
		line_1.x2 = x;
		line_1.y2 = y;
		line_2 = line_1;

		// set line size
		if( dir == DIR_HORIZONTAL )
		{
			line_1.x1 += csize;
			line_2.x2 -= csize;
		}
		else // vertical
		{
			line_1.y1 += csize;
			line_2.y2 -= csize;
		}

		// debug drawing
		if( pOverworld_Manager->debugmode && pOverworld_Manager->draw_layer )
		{
			// create request
			cLineRequest *line_request = new cLineRequest();
			pVideo->Draw_Line( line_1.x1 - pActive_Camera->x, line_1.y1 - pActive_Camera->y, line_1.x2 - pActive_Camera->x, line_1.y2 - pActive_Camera->y, map_layer_line->posz + 0.001f, &white, line_request );
			line_request->line_width = 2;
			line_request->render_count = 50;
			// add request
			pRenderer->Add( line_request );

			// create request
			line_request = new cLineRequest();
			pVideo->Draw_Line( line_2.x1 - pActive_Camera->x, line_2.y1 - pActive_Camera->y, line_2.x2 - pActive_Camera->x, line_2.y2 - pActive_Camera->y, map_layer_line->posz + 0.001f, &black, line_request );
			line_request->line_width = 2;
			line_request->render_count = 50;
			// add request
			pRenderer->Add( line_request );
		}

		// check direction line 1
		if( Col_Line( &line_1, &map_line ) )
		{
			cLine_collision col = cLine_collision();

			col.line = map_layer_line;
			col.line_number = Get_Array_Num( map_layer_line );
			col.difference = csize;

			// found
			return col;
		}

		// check direction line 2
		if( Col_Line( &line_2, &map_line ) )
		{
			cLine_collision col = cLine_collision();

			col.line = map_layer_line;
			col.line_number = Get_Array_Num( map_layer_line );
			col.difference = -csize;

			// found
			return col;
		}
	}

	// not found
	return cLine_collision();
}

// XML element start
void cLayer :: elementStart( const CEGUI::String &element, const CEGUI::XMLAttributes &attributes )
{
	// Property of an Element
    if( element == "Property" )
    {
		xml_attributes.add( attributes.getValueAsString( "name" ), attributes.getValueAsString( "value" ) );
    }
}

// XML element end
void cLayer :: elementEnd( const CEGUI::String &element )
{
	if( element != "Property" )
	{
		if( element == "line" )
		{
			// add layer line
			Add( new cLayer_Line_Point_Start( xml_attributes ) );
		}
		else if( element == "layer" )
		{
			// ignore
		}
		else if( element.length() )
		{
			printf( "Warning : Overworld Layer Unknown element : %s\n", element.c_str() );
		}

		// clear
		xml_attributes = CEGUI::XMLAttributes();
	}
}
