// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// 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 2 of the License, or (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#include "idocument.h"
#include "imesh_sink.h"
#include "imouse_event_observer.h"
#include "iobject_collection.h"
#include "iselectable.h"
#include "iselection.h"
#include "iobject.h"
#include "mesh.h"
#include "property.h"
#include "result.h"
#include "selection.h"
#include "utility.h"

#include <iterator>

namespace
{

class expand_selection
{
public:
	expand_selection(k3d::idag& Dag, k3d::iselection::selection_t& Selection) :
		m_dag(Dag),
		m_selection(Selection)
	{
	}

	void add_mesh(k3d::mesh& Mesh)
	{
		m_selection.insert(Mesh.points.begin(), Mesh.points.end());
		m_selection.insert(Mesh.point_groups.begin(), Mesh.point_groups.end());
		
		for(k3d::mesh::polyhedra_t::iterator polyhedron = Mesh.polyhedra.begin(); polyhedron != Mesh.polyhedra.end(); ++polyhedron)
			{
				m_selection.insert(*polyhedron);
				m_selection.insert((*polyhedron)->edges.begin(), (*polyhedron)->edges.end());
				m_selection.insert((*polyhedron)->faces.begin(), (*polyhedron)->faces.end());
			}
		
		for(k3d::mesh::linear_curve_groups_t::const_iterator group = Mesh.linear_curve_groups.begin(); group != Mesh.linear_curve_groups.end(); ++group)
			{
				m_selection.insert(*group);
				m_selection.insert((*group)->curves.begin(), (*group)->curves.end());
			}
		
		for(k3d::mesh::cubic_curve_groups_t::const_iterator group = Mesh.cubic_curve_groups.begin(); group != Mesh.cubic_curve_groups.end(); ++group)
			{
				m_selection.insert(*group);
				m_selection.insert((*group)->curves.begin(), (*group)->curves.end());
			}
		
		for(k3d::mesh::nucurve_groups_t::const_iterator group = Mesh.nucurve_groups.begin(); group != Mesh.nucurve_groups.end(); ++group)
			{
				m_selection.insert(*group);
				m_selection.insert((*group)->curves.begin(), (*group)->curves.end());
			}
		
		m_selection.insert(Mesh.bilinear_patches.begin(), Mesh.bilinear_patches.end());
		m_selection.insert(Mesh.bicubic_patches.begin(), Mesh.bicubic_patches.end());
		m_selection.insert(Mesh.nupatches.begin(), Mesh.nupatches.end());
	}
			
	void operator()(k3d::iselectable* Object)
	{
		k3d::imesh_sink* const mesh_sink = dynamic_cast<k3d::imesh_sink*>(Object);
		if(mesh_sink)
			{
				k3d::mesh* const mesh = boost::any_cast<k3d::mesh*>(k3d::get_property_value(m_dag, mesh_sink->mesh_sink_input()));
				if(mesh)
					add_mesh(*mesh);
					
				return;
			}
	
		k3d::mesh* const mesh = dynamic_cast<k3d::mesh*>(Object);
		if(mesh)
			{
				add_mesh(*mesh);
				return;
			}
		
		k3d::point_group* const point_group = dynamic_cast<k3d::point_group*>(Object);
		if(point_group)
			{
				m_selection.insert(point_group->points.begin(), point_group->points.end());
				return;
			}
		
		k3d::split_edge* const split_edge = dynamic_cast<k3d::split_edge*>(Object);
		if(split_edge)
			{
				m_selection.insert(split_edge->vertex);
				
				if(split_edge->companion)
					{
						m_selection.insert(split_edge->companion);
						m_selection.insert(split_edge->companion->vertex);
					}
					
				return;
			}
		
		k3d::face* const face = dynamic_cast<k3d::face*>(Object);
		if(face)
			{
				for(k3d::split_edge* edge = face->first_edge; edge; edge = edge->face_clockwise)
					{
						m_selection.insert(edge);
						m_selection.insert(edge->vertex);
						if(face->first_edge == edge->face_clockwise)
							break;
					}

				for(k3d::face::holes_t::const_iterator hole = face->holes.begin(); hole != face->holes.end(); ++hole)
					{
						for(k3d::split_edge* edge = *hole; edge; edge = edge->face_clockwise)
							{
								m_selection.insert(edge);
								m_selection.insert(edge->vertex);
								if(*hole == edge->face_clockwise)
									break;
							}
					}
												
				return;
			}

		k3d::linear_curve* const linear_curve = dynamic_cast<k3d::linear_curve*>(Object);
		if(linear_curve)
			{
				m_selection.insert(linear_curve->control_points.begin(), linear_curve->control_points.end());
			}
						
		k3d::cubic_curve* const cubic_curve = dynamic_cast<k3d::cubic_curve*>(Object);
		if(cubic_curve)
			{
				m_selection.insert(cubic_curve->control_points.begin(), cubic_curve->control_points.end());
			}
						
		k3d::bilinear_patch* const bilinear_patch = dynamic_cast<k3d::bilinear_patch*>(Object);
		if(bilinear_patch)
			{
				m_selection.insert(bilinear_patch->control_points.begin(), bilinear_patch->control_points.end());
				return;
			}
		
		k3d::bicubic_patch* const bicubic_patch = dynamic_cast<k3d::bicubic_patch*>(Object);
		if(bicubic_patch)
			{
				m_selection.insert(bicubic_patch->control_points.begin(), bicubic_patch->control_points.end());
				return;
			}

		k3d::nucurve* const nucurve = dynamic_cast<k3d::nucurve*>(Object);
		if(nucurve)
			{
				for(k3d::nucurve::control_points_t::const_iterator cv = nucurve->control_points.begin(); cv != nucurve->control_points.end(); ++cv)
					m_selection.insert(cv->position);
				return;
			}
			
		k3d::nupatch*const nupatch = dynamic_cast<k3d::nupatch*>(Object);
		if(nupatch)
			{
				for(k3d::nupatch::control_points_t::const_iterator cv = nupatch->control_points.begin(); cv != nupatch->control_points.end(); ++cv)
					m_selection.insert(cv->position);
				return;
			}
	}

private:
	k3d::idag& m_dag;
	k3d::iselection::selection_t& m_selection;
};

struct is_selected
{
	template<typename T>
	bool operator()(T* Object)
	{
		return Object->selected;
	}
};

} // namespace

namespace k3d
{

const bool contains_selection(const mesh& Mesh)
{
	if(Mesh.selected)
		return true;

	if(std::find_if(Mesh.points.begin(), Mesh.points.end(), is_selected()) != Mesh.points.end())
		return true;

	if(std::find_if(Mesh.point_groups.begin(), Mesh.point_groups.end(), is_selected()) != Mesh.point_groups.end())
		return true;

	for(k3d::mesh::polyhedra_t::const_iterator polyhedron = Mesh.polyhedra.begin(); polyhedron != Mesh.polyhedra.end(); ++polyhedron)
		{
			if((*polyhedron)->selected)
				return true;
				
			if(std::find_if((*polyhedron)->edges.begin(), (*polyhedron)->edges.end(), is_selected()) != (*polyhedron)->edges.end())
				return true;

			if(std::find_if((*polyhedron)->faces.begin(), (*polyhedron)->faces.end(), is_selected()) != (*polyhedron)->faces.end())
				return true;
		}

	for(k3d::mesh::linear_curve_groups_t::const_iterator group = Mesh.linear_curve_groups.begin(); group != Mesh.linear_curve_groups.end(); ++group)
		{
			if((*group)->selected)
				return true;
				
			if(std::find_if((*group)->curves.begin(), (*group)->curves.end(), is_selected()) != (*group)->curves.end())
				return true;
		}

	for(k3d::mesh::cubic_curve_groups_t::const_iterator group = Mesh.cubic_curve_groups.begin(); group != Mesh.cubic_curve_groups.end(); ++group)
		{
			if((*group)->selected)
				return true;
				
			if(std::find_if((*group)->curves.begin(), (*group)->curves.end(), is_selected()) != (*group)->curves.end())
				return true;
		}
		
	for(k3d::mesh::nucurve_groups_t::const_iterator group = Mesh.nucurve_groups.begin(); group != Mesh.nucurve_groups.end(); ++group)
		{
			if((*group)->selected)
				return true;
				
			if(std::find_if((*group)->curves.begin(), (*group)->curves.end(), is_selected()) != (*group)->curves.end())
				return true;
		}
		
	if(std::find_if(Mesh.bilinear_patches.begin(), Mesh.bilinear_patches.end(), is_selected()) != Mesh.bilinear_patches.end())
		return true;

	if(std::find_if(Mesh.bicubic_patches.begin(), Mesh.bicubic_patches.end(), is_selected()) != Mesh.bicubic_patches.end())
		return true;

	if(std::find_if(Mesh.nupatches.begin(), Mesh.nupatches.end(), is_selected()) != Mesh.nupatches.end())
		return true;

	return false;
}

const iselection::selection_t make_selection(iunknown& Object)
{
	iselection::selection_t result;
	result.insert(dynamic_cast<iselectable*>(&Object));
	return result;
}

const iselection::selection_t deep_selection(idag& Dag, const iselection::selection_t& Selection)
{
	iselection::selection_t result(Selection);
	std::for_each(Selection.begin(), Selection.end(), expand_selection(Dag, result));

	return result;
}

void select(idocument& Document, const iselection::selection_t& Selection)
{
	std::for_each(Selection.begin(), Selection.end(), std::mem_fun(&k3d::iselectable::select));
	Document.selection().selected_signal().emit(Selection);
}

void deselect(idocument& Document, const iselection::selection_t& Selection)
{
	std::for_each(Selection.begin(), Selection.end(), std::mem_fun(&k3d::iselectable::deselect));
	Document.selection().deselected_signal().emit(Selection);
}

void deselect_all(idocument& Document)
{
	Document.selection().deselect_all_signal().emit();
}

void set_mouse_focus(idocument& Document, iunknown& Object)
{
	k3d::imouse_event_observer* const mouse_observer = dynamic_cast<k3d::imouse_event_observer*>(&Object);
	if(mouse_observer)
		Document.set_mouse_focus(mouse_observer);
}

void release_mouse_focus(idocument& Document, iunknown& Object)
{
	if(Document.mouse_focus() == dynamic_cast<imouse_event_observer*>(&Object))
		Document.set_mouse_focus(0);
}

} // namespace k3d

