/* $Id$ */
 
/**
 * \file 
 * $Revision$
 * $Date$
 * 
 * Copyright (C) 2010, 2011 The University of Sydney, Australia
 *
 * This file is part of GPlates.
 *
 * GPlates is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, version 2, as published by
 * the Free Software Foundation.
 *
 * GPlates 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 Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <boost/foreach.hpp>

#include "VisualLayer.h"

#include "LayerOutputRenderer.h"
#include "ReconstructionGeometryRenderer.h"
#include "VisualLayerRegistry.h"
#include "VisualLayers.h"
#include "ViewState.h"

#include "app-logic/ApplicationState.h"
#include "app-logic/Layer.h"
#include "app-logic/Reconstruction.h"

#include "gui/Symbol.h"

#include "file-io/FileInfo.h"

#include "view-operations/RenderedGeometryParameters.h"


GPlatesPresentation::VisualLayer::VisualLayer(
		ViewState &view_state,
		VisualLayers &visual_layers,
		GPlatesAppLogic::Layer &layer,
		int layer_number) :
	d_visual_layers(visual_layers),
	d_visual_layer_registry(view_state.get_visual_layer_registry()),
	d_rendered_geometry_parameters(view_state.get_rendered_geometry_parameters()),
	d_render_settings(view_state.get_render_settings()),
	d_symbol_map(view_state.get_feature_type_symbol_map()),
	d_application_state(view_state.get_application_state()),
	d_layer(layer),
	// Create a child rendered geometry layer in the main RECONSTRUCTION layer.
	d_rendered_geometry_layer_index(
			view_state.get_rendered_geometry_collection().create_child_rendered_layer(
					GPlatesViewOperations::RenderedGeometryCollection::RECONSTRUCTION_LAYER)),
	d_rendered_geometry_layer(
			view_state.get_rendered_geometry_collection().transfer_ownership_of_child_rendered_layer(
					d_rendered_geometry_layer_index,
					GPlatesViewOperations::RenderedGeometryCollection::RECONSTRUCTION_LAYER)),
	d_visible(true),
	d_layer_number(layer_number),
	d_visual_layer_params(
			d_visual_layer_registry.create_visual_layer_params(
					get_layer_type(),
					layer.get_layer_params()))
{
	d_widget_sections_expanded[ALL] = false;
	d_widget_sections_expanded[INPUT_CHANNELS] = true;
	d_widget_sections_expanded[LAYER_OPTIONS] = true;
	d_widget_sections_expanded[ADVANCED_OPTIONS] = false;

	d_visual_layer_params->handle_layer_modified(layer);

	QObject::connect(
			d_layer.get_layer_params().get(),
			SIGNAL(modified(GPlatesAppLogic::LayerParams &)),
			this,
			SLOT(handle_params_modified()));
	QObject::connect(
			d_visual_layer_params.get(),
			SIGNAL(modified()),
			this,
			SLOT(handle_params_modified()));
}


GPlatesPresentation::VisualLayerType::Type
GPlatesPresentation::VisualLayer::get_layer_type() const
{
	return static_cast<VisualLayerType::Type>(d_layer.get_type());
}


void
GPlatesPresentation::VisualLayer::create_rendered_geometries()
{
	// Delay any notification of changes to the rendered geometry collection
	// until end of current scope block. This is so we can do multiple changes
	// without any canvas' redrawing themselves after each change.
	// This should ideally be located at the highest level to capture one
	// user GUI interaction - the user performs an action and we update canvas once.
	// But since these guards can be nested it's probably a good idea to have it here too.
	GPlatesViewOperations::RenderedGeometryCollection::UpdateGuard update_guard;

	// Activate the layer.
	d_rendered_geometry_layer->set_active();

	// Clear all RenderedGeometry's before adding new ones.
	d_rendered_geometry_layer->clear_rendered_geometries();	

	// Don't create RenderedGeometry's if hidden.
	if (!d_visible)
	{
		return;
	}

	// Don't create RenderedGeometry's if the registry says we don't.
	if (!d_visual_layer_registry.produces_rendered_geometries(get_layer_type()))
	{
		return;
	}

	// Get the most recent output data generated by this layer and see if it contains
	// reconstruction geometries.
	boost::optional<GPlatesAppLogic::LayerProxy::non_null_ptr_type> layer_output =
			d_layer.get_layer_output();
	if (!layer_output)
	{
		// Return with an empty rendered geometry layer.
		return;
	}

	// Get the draw style adapter if there is one.
	boost::optional<const GPlatesGui::StyleAdapter &> draw_style_adapter;
	if (d_visual_layer_params->style_adapter())
	{
		draw_style_adapter = *d_visual_layer_params->style_adapter();
	}

	// This creates the RenderedGeometry's from the ReconstructionGeometry's.
	ReconstructionGeometryRenderer::RenderParamsPopulator render_params_populator(
			d_rendered_geometry_parameters);
	d_visual_layer_params->accept_visitor(render_params_populator);
	ReconstructionGeometryRenderer reconstruction_geometry_renderer(
			render_params_populator.get_render_params(),
			d_render_settings,
			d_application_state.get_current_topological_sections(),
			d_application_state.get_current_reconstruction().get_all_resolved_topological_shared_sub_segments(),
			boost::none, // colour 
			boost::none, // rotation adjustment
			d_symbol_map,
			draw_style_adapter);

	// Visit the layer output in order to render it.
	// We wrap the ReconstructionGeometryRenderer with a LayerOutputRenderer.
	// The latter delegates to the former for actual rendering.
	LayerOutputRenderer layer_output_renderer(
			reconstruction_geometry_renderer,
			*d_rendered_geometry_layer);
	layer_output.get()->accept_visitor(layer_output_renderer);
}


bool
GPlatesPresentation::VisualLayer::is_expanded(
		WidgetSection section) const
{
	return d_widget_sections_expanded[section];
}


void
GPlatesPresentation::VisualLayer::set_expanded(
		WidgetSection section,
		bool expanded)
{
	if (expanded != d_widget_sections_expanded[section])
	{
		d_widget_sections_expanded[section] = expanded;
		emit_layer_modified();
	}
}


void
GPlatesPresentation::VisualLayer::toggle_expanded(
		WidgetSection section)
{
	d_widget_sections_expanded[section] = !d_widget_sections_expanded[section];
	emit_layer_modified();
}


bool
GPlatesPresentation::VisualLayer::is_visible() const
{
	return d_visible;
}


void
GPlatesPresentation::VisualLayer::set_visible(
		bool visible)
{
	if (visible != d_visible)
	{
		d_visible = visible;

		// Clear the rendered geometries, or re-create rendered geometries, as needed.
		create_rendered_geometries();

		// We do this after creating the rendered geometries in case clients search
		// through the rendered geometries to find visible reconstruction geometries.
		emit_layer_modified();
	}
}


void
GPlatesPresentation::VisualLayer::toggle_visible()
{
	d_visible = !d_visible;

	// Clear the rendered geometries, or re-create rendered geometries, as needed.
	create_rendered_geometries();

	// We do this after creating the rendered geometries in case clients search
	// through the rendered geometries to find visible reconstruction geometries.
	emit_layer_modified();
}


QString
GPlatesPresentation::VisualLayer::get_generated_name() const
{
	// Get the (feature collection) inputs on the main channel.
	typedef GPlatesAppLogic::Layer::InputConnection InputConnection;
	GPlatesAppLogic::LayerInputChannelName::Type main_channel =
			d_layer.get_main_input_feature_collection_channel();
	std::vector<InputConnection> inputs = d_layer.get_channel_inputs(main_channel);

	// Use the first input that has an InputFile.
	typedef GPlatesAppLogic::Layer::InputFile InputFile;
	QString result;
	BOOST_FOREACH(InputConnection &input_connection, inputs)
	{
		boost::optional<InputFile> input_file = input_connection.get_input_file();
		if (input_file)
		{
			result = input_file->get_file_info().get_file_name_without_extension();
			break;
		}
	}

	// If we can't use an InputFile's name, then we generate a name from the layer number.
	if (result.isEmpty())
	{
		result = QString("Layer %1").arg(d_layer_number);
	}

	return result;
}


const boost::optional<QString> &
GPlatesPresentation::VisualLayer::get_custom_name() const
{
	return d_custom_name;
}


void
GPlatesPresentation::VisualLayer::set_custom_name(
		const boost::optional<QString> &custom_name)
{
	d_custom_name = custom_name;
	d_visual_layers.refresh_all_layers();
}


QString
GPlatesPresentation::VisualLayer::get_name() const
{
	if (d_custom_name)
	{
		return *d_custom_name;
	}
	else
	{
		return get_generated_name();
	}
}


GPlatesPresentation::VisualLayerParams::non_null_ptr_type
GPlatesPresentation::VisualLayer::get_visual_layer_params()
{
	return d_visual_layer_params;
}


GPlatesPresentation::VisualLayerParams::non_null_ptr_to_const_type
GPlatesPresentation::VisualLayer::get_visual_layer_params() const
{
	return d_visual_layer_params;
}


void
GPlatesPresentation::VisualLayer::handle_params_modified()
{
	// Clear the rendered geometries, or re-create rendered geometries, as needed.
	create_rendered_geometries();

	// We do this after creating the rendered geometries in case clients search
	// through the rendered geometries to find visible reconstruction geometries.
	emit_layer_modified();
}


void
GPlatesPresentation::VisualLayer::emit_layer_modified()
{
	// Emit signal via VisualLayers.
	d_visual_layers.emit_layer_modified(d_rendered_geometry_layer_index);
}

