/*
   Copyright (C) 2001-2006, William Joseph.
   All Rights Reserved.

   This file is part of GtkRadiant.

   GtkRadiant 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 2 of the License, or
   (at your option) any later version.

   GtkRadiant 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#if !defined( INCLUDED_TARGETABLE_H )
#define INCLUDED_TARGETABLE_H

#include <set>
#include <map>

#include "cullable.h"
#include "renderable.h"

#include "math/line.h"
#include "render.h"
#include "generic/callback.h"
#include "selectionlib.h"
#include "entitylib.h"
#include "eclasslib.h"
#include "stringio.h"

class Targetable
{
public:
virtual const Vector3& world_position() const = 0;
};

typedef std::set<Targetable*> targetables_t;

extern const char* g_targetable_nameKey;

targetables_t* getTargetables( const char* targetname );

class EntityConnectionLine : public OpenGLRenderable
{
public:
Vector3 start;
Vector3 end;

void render( RenderStateFlags state ) const {
	float s1[2], s2[2];
	Vector3 dir( vector3_subtracted( end, start ) );
	double len = vector3_length( dir );
	vector3_scale( dir, 8.0 * ( 1.0 / len ) );
	s1[0] = dir[0] - dir[1];
	s1[1] = dir[0] + dir[1];
	s2[0] = dir[0] + dir[1];
	s2[1] = -dir[0] + dir[1];

	glBegin( GL_LINES );

	glVertex3fv( vector3_to_array( start ) );
	glVertex3fv( vector3_to_array( end ) );

	len *= 0.0625; // half / 8

	Vector3 arrow( start );
	for ( unsigned int i = 0, count = ( len < 32 ) ? 1 : static_cast<unsigned int>( len * 0.0625 ); i < count; i++ )
	{
		vector3_add( arrow, vector3_scaled( dir, ( len < 32 ) ? len : 32 ) );
		glVertex3fv( vector3_to_array( arrow ) );
		glVertex3f( arrow[0] + s1[0], arrow[1] + s1[1], arrow[2] + dir[2] );
		glVertex3fv( vector3_to_array( arrow ) );
		glVertex3f( arrow[0] + s2[0], arrow[1] + s2[1], arrow[2] + dir[2] );
	}

	glEnd();
}
};

class TargetedEntity
{
Targetable& m_targetable;
targetables_t* m_targets;

void construct(){
	if ( m_targets != 0 ) {
		m_targets->insert( &m_targetable );
	}
}
void destroy(){
	if ( m_targets != 0 ) {
		m_targets->erase( &m_targetable );
	}
}
public:
TargetedEntity( Targetable& targetable )
	: m_targetable( targetable ), m_targets( getTargetables( "" ) ){
	construct();
}
~TargetedEntity(){
	destroy();
}
void targetnameChanged( const char* name ){
	destroy();
	m_targets = getTargetables( name );
	construct();
}
typedef MemberCaller<TargetedEntity, void(const char*), &TargetedEntity::targetnameChanged> TargetnameChangedCaller;
};


class TargetingEntity
{
targetables_t* m_targets;
public:
TargetingEntity() :
	m_targets( getTargetables( "" ) ){
}
void targetChanged( const char* target ){
	m_targets = getTargetables( target );
}
typedef MemberCaller<TargetingEntity, void(const char*), &TargetingEntity::targetChanged> TargetChangedCaller;

typedef targetables_t::iterator iterator;

iterator begin() const {
	if ( m_targets == 0 ) {
		return iterator();
	}
	return m_targets->begin();
}
iterator end() const {
	if ( m_targets == 0 ) {
		return iterator();
	}
	return m_targets->end();
}
size_t size() const {
	if ( m_targets == 0 ) {
		return 0;
	}
	return m_targets->size();
}
bool empty() const {
	return m_targets == 0 || m_targets->empty();
}
};



template<typename Functor>
void TargetingEntity_forEach( const TargetingEntity& targets, const Functor& functor ){
	for ( TargetingEntity::iterator i = targets.begin(); i != targets.end(); ++i )
	{
		functor( ( *i )->world_position() );
	}
}

typedef std::map<std::size_t, TargetingEntity> TargetingEntities;

template<typename Functor>
void TargetingEntities_forEach( const TargetingEntities& targetingEntities, const Functor& functor ){
	for ( TargetingEntities::const_iterator i = targetingEntities.begin(); i != targetingEntities.end(); ++i )
	{
		TargetingEntity_forEach( ( *i ).second, functor );
	}
}

class TargetLinesPushBack
{
RenderablePointVector& m_targetLines;
const Vector3& m_worldPosition;
const VolumeTest& m_volume;
public:
TargetLinesPushBack( RenderablePointVector& targetLines, const Vector3& worldPosition, const VolumeTest& volume ) :
	m_targetLines( targetLines ), m_worldPosition( worldPosition ), m_volume( volume ){
}
void operator()( const Vector3& worldPosition ) const {
	Vector3 dir( worldPosition - m_worldPosition );//end - start
	double len = vector3_length( dir );
	if ( len != 0 && m_volume.TestLine( segment_for_startend( m_worldPosition, worldPosition ) ) ) {
		m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( m_worldPosition ) ) );
		m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( worldPosition ) ) );

		Vector3 mid( ( worldPosition + m_worldPosition ) * 0.5f );
		//vector3_normalise( dir );
		dir /= len;
		Vector3 hack( 0.57735, 0.57735, 0.57735 );
		int maxI = 0;
		float max = 0;
		for ( int i = 0; i < 3 ; ++i ){
			if ( dir[i] < 0 ){
				hack[i] *= -1;
			}
			if ( fabs( dir[i] ) > max ){
				maxI = i;
			}
		}
		hack[maxI] *= -1;

		Vector3 ort( vector3_cross( dir, hack ) );
		//vector3_normalise( ort );
		Vector3 wing1( mid - dir*12 + ort*6 );
		Vector3 wing2( wing1 - ort*12 );

		if( len <= 512 || len > 768 ){
			m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( mid ) ) );
			m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( wing1 ) ) );
			m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( mid ) ) );
			m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( wing2 ) ) );
		}
		if( len > 512 ){
			Vector3 wing1_delta( mid - wing1 );
			Vector3 wing2_delta( mid - wing2 );
			Vector3 point( m_worldPosition + dir*256 );
			wing1 = point - wing1_delta;
			wing2 = point - wing2_delta;
			m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( point ) ) );
			m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( wing1 ) ) );
			m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( point ) ) );
			m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( wing2 ) ) );
			point =  worldPosition - dir*256;
			wing1 = point - wing1_delta;
			wing2 = point - wing2_delta;
			m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( point ) ) );
			m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( wing1 ) ) );
			m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( point ) ) );
			m_targetLines.push_back( PointVertex( reinterpret_cast<const Vertex3f&>( wing2 ) ) );
		}
	}
}
};

class TargetKeys : public Entity::Observer
{
TargetingEntities m_targetingEntities;
Callback<void()> m_targetsChanged;

bool readTargetKey( const char* key, std::size_t& index ){
	if ( string_equal_n( key, "target", 6 ) ) {
		index = 0;
		if ( string_empty( key + 6 ) || string_parse_size( key + 6, index ) ) {
			return true;
		}
	}
	if ( string_equal( key, "killtarget" ) ) {
		index = -1;
		return true;
	}
	return false;
}
public:
void setTargetsChanged( const Callback<void()>& targetsChanged ){
	m_targetsChanged = targetsChanged;
}
void targetsChanged(){
	m_targetsChanged();
}

void insert( const char* key, EntityKeyValue& value ){
	std::size_t index;
	if ( readTargetKey( key, index ) ) {
		TargetingEntities::iterator i = m_targetingEntities.insert( TargetingEntities::value_type( index, TargetingEntity() ) ).first;
		value.attach( TargetingEntity::TargetChangedCaller( ( *i ).second ) );
		targetsChanged();
	}
}
void erase( const char* key, EntityKeyValue& value ){
	std::size_t index;
	if ( readTargetKey( key, index ) ) {
		TargetingEntities::iterator i = m_targetingEntities.find( index );
		value.detach( TargetingEntity::TargetChangedCaller( ( *i ).second ) );
		m_targetingEntities.erase( i );
		targetsChanged();
	}
}
const TargetingEntities& get() const {
	return m_targetingEntities;
}
};



class RenderableTargetingEntity
{
TargetingEntity& m_targets;
mutable RenderablePointVector m_target_lines;
public:
static Shader* m_state;

RenderableTargetingEntity( TargetingEntity& targets )
	: m_targets( targets ), m_target_lines( GL_LINES ){
}
void compile( const VolumeTest& volume, const Vector3& world_position ) const {
	m_target_lines.clear();
	m_target_lines.reserve( m_targets.size() * 14 );
	TargetingEntity_forEach( m_targets, TargetLinesPushBack( m_target_lines, world_position, volume ) );
}
void render( Renderer& renderer, const VolumeTest& volume, const Vector3& world_position ) const {
	if ( !m_targets.empty() ) {
		compile( volume, world_position );
		if ( !m_target_lines.empty() ) {
			renderer.addRenderable( m_target_lines, g_matrix4_identity );
		}
	}
}
};

class RenderableTargetingEntities
{
const TargetingEntities& m_targets;
mutable RenderablePointVector m_target_lines;
public:
static Shader* m_state;

RenderableTargetingEntities( const TargetingEntities& targets )
	: m_targets( targets ), m_target_lines( GL_LINES ){
}
void compile( const VolumeTest& volume, const Vector3& world_position ) const {
	m_target_lines.clear();
	TargetingEntities_forEach( m_targets, TargetLinesPushBack( m_target_lines, world_position, volume ) );
}
void render( Renderer& renderer, const VolumeTest& volume, const Vector3& world_position ) const {
	if ( !m_targets.empty() ) {
		compile( volume, world_position );
		if ( !m_target_lines.empty() ) {
			renderer.addRenderable( m_target_lines, g_matrix4_identity );
		}
	}
}
};


class TargetableInstance :
	public SelectableInstance,
	public Targetable,
	public Entity::Observer
{
mutable Vertex3f m_position;
EntityKeyValues& m_entity;
TargetKeys m_targeting;
TargetedEntity m_targeted;
RenderableTargetingEntities m_renderable;
public:

TargetableInstance(
	const scene::Path& path,
	scene::Instance* parent,
	void* instance,
	InstanceTypeCastTable& casts,
	EntityKeyValues& entity,
	Targetable& targetable
	) :
	SelectableInstance( path, parent, instance, casts ),
	m_entity( entity ),
	m_targeted( targetable ),
	m_renderable( m_targeting.get() ){
	m_entity.attach( *this );
	m_entity.attach( m_targeting );
}
~TargetableInstance(){
	m_entity.detach( m_targeting );
	m_entity.detach( *this );
}

void setTargetsChanged( const Callback<void()>& targetsChanged ){
	m_targeting.setTargetsChanged( targetsChanged );
}
void targetsChanged(){
	m_targeting.targetsChanged();
}

void insert( const char* key, EntityKeyValue& value ){
	if ( string_equal( key, g_targetable_nameKey ) ) {
		value.attach( TargetedEntity::TargetnameChangedCaller( m_targeted ) );
	}
}
void erase( const char* key, EntityKeyValue& value ){
	if ( string_equal( key, g_targetable_nameKey ) ) {
		value.detach( TargetedEntity::TargetnameChangedCaller( m_targeted ) );
	}
}

const Vector3& world_position() const {
#if 1
	const AABB& bounds = Instance::worldAABB();
	if ( aabb_valid( bounds ) ) {
		return bounds.origin;
	}
#else
	const AABB& childBounds = Instance::childBounds();
	if ( aabb_valid( childBounds ) ) {
		return childBounds.origin;
	}
#endif
	return vector4_to_vector3( localToWorld().t() );
}

void render( Renderer& renderer, const VolumeTest& volume ) const {
	renderer.SetState( m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly );
	renderer.SetState( m_entity.getEntityClass().m_state_wire, Renderer::eFullMaterials );
	m_renderable.render( renderer, volume, world_position() );
}

const TargetingEntities& getTargeting() const {
	return m_targeting.get();
}
};


class RenderableConnectionLines : public Renderable
{
typedef std::set<TargetableInstance*> TargetableInstances;
TargetableInstances m_instances;
public:
void attach( TargetableInstance& instance ){
	ASSERT_MESSAGE( m_instances.find( &instance ) == m_instances.end(), "cannot attach instance" );
	m_instances.insert( &instance );
}
void detach( TargetableInstance& instance ){
	ASSERT_MESSAGE( m_instances.find( &instance ) != m_instances.end(), "cannot detach instance" );
	m_instances.erase( &instance );
}

void renderSolid( Renderer& renderer, const VolumeTest& volume ) const {
	for ( TargetableInstances::const_iterator i = m_instances.begin(); i != m_instances.end(); ++i )
	{
		if ( ( *i )->path().top().get().visible() ) {
			( *i )->render( renderer, volume );
		}
	}
}
void renderWireframe( Renderer& renderer, const VolumeTest& volume ) const {
	renderSolid( renderer, volume );
}
};

typedef Static<RenderableConnectionLines> StaticRenderableConnectionLines;

#endif
