/* $Id$ */
 
/**
 * \file 
 * $Revision$
 * $Date$
 * 
 * Copyright (C) 2010 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 <algorithm>
#include <boost/bind/bind.hpp>

#include "TopologyNetworkResolverLayerTask.h"

#include "AppLogicUtils.h"
#include "ReconstructUtils.h"
#include "TopologyGeometryResolverLayerProxy.h"
#include "TopologyUtils.h"


GPlatesAppLogic::TopologyNetworkResolverLayerTask::TopologyNetworkResolverLayerTask() :
		d_layer_params(TopologyNetworkLayerParams::create()),
		d_topology_network_resolver_layer_proxy(TopologyNetworkResolverLayerProxy::create())
{
	// Notify our layer output whenever the layer params are modified.
	QObject::connect(
			d_layer_params.get(), SIGNAL(modified_topology_network_params(GPlatesAppLogic::TopologyNetworkLayerParams &)),
			this, SLOT(handle_topology_network_params_modified(GPlatesAppLogic::TopologyNetworkLayerParams &)));
}


bool
GPlatesAppLogic::TopologyNetworkResolverLayerTask::can_process_feature_collection(
		const GPlatesModel::FeatureCollectionHandle::const_weak_ref &feature_collection)
{
	return TopologyUtils::has_topological_network_features(feature_collection);
}


std::vector<GPlatesAppLogic::LayerInputChannelType>
GPlatesAppLogic::TopologyNetworkResolverLayerTask::get_input_channel_types() const
{
	std::vector<LayerInputChannelType> input_channel_types;

	// NOTE: There's no channel definition for a reconstruction tree - a rotation layer is not needed.

	// Channel definition for the topological network features.
	input_channel_types.push_back(
			LayerInputChannelType(
					LayerInputChannelName::TOPOLOGICAL_NETWORK_FEATURES,
					LayerInputChannelType::MULTIPLE_DATAS_IN_CHANNEL));

	// Channel definition for the topological section layers.
	// - reconstructed geometries, or
	// - resolved topological lines.
	//
	// The referenced reconstructed topological section geometries are obtained by referencing
	// the weak observers of referenced features (ReconstructedFeatureGeometry is a weak observer
	// of a feature). By default, if there are no connections on this channel, this is a global
	// search through all loaded features. However if there are any connections then the global
	// search is restricted to reconstructed geometries and resolved topological lines that
	// generated by the connected layers..
	std::vector<LayerTaskType::Type> topological_section_channel_types;
	topological_section_channel_types.push_back(LayerTaskType::RECONSTRUCT);
	topological_section_channel_types.push_back(LayerTaskType::TOPOLOGY_GEOMETRY_RESOLVER);
	input_channel_types.push_back(
			LayerInputChannelType(
					LayerInputChannelName::TOPOLOGICAL_SECTION_LAYERS,
					LayerInputChannelType::MULTIPLE_DATAS_IN_CHANNEL,
					topological_section_channel_types));

	return input_channel_types;
}


GPlatesAppLogic::LayerInputChannelName::Type
GPlatesAppLogic::TopologyNetworkResolverLayerTask::get_main_input_feature_collection_channel() const
{
	return LayerInputChannelName::TOPOLOGICAL_NETWORK_FEATURES;
}


void
GPlatesAppLogic::TopologyNetworkResolverLayerTask::activate(
		bool active)
{
	// If deactivated then specify an empty set of topological sections layer proxies.
	if (!active)
	{
		// Set the current topological sections.
		d_topology_network_resolver_layer_proxy->set_current_topological_sections_layer_proxies(
				std::vector<ReconstructLayerProxy::non_null_ptr_type>(),
				std::vector<TopologyGeometryResolverLayerProxy::non_null_ptr_type>());
	}
}


void
GPlatesAppLogic::TopologyNetworkResolverLayerTask::add_input_file_connection(
		LayerInputChannelName::Type input_channel_name,
		const GPlatesModel::FeatureCollectionHandle::weak_ref &feature_collection)
{
	if (input_channel_name == LayerInputChannelName::TOPOLOGICAL_NETWORK_FEATURES)
	{
		d_topology_network_resolver_layer_proxy
				->add_topological_network_feature_collection(feature_collection);
	}
}


void
GPlatesAppLogic::TopologyNetworkResolverLayerTask::remove_input_file_connection(
		LayerInputChannelName::Type input_channel_name,
		const GPlatesModel::FeatureCollectionHandle::weak_ref &feature_collection)
{
	if (input_channel_name == LayerInputChannelName::TOPOLOGICAL_NETWORK_FEATURES)
	{
		d_topology_network_resolver_layer_proxy
				->remove_topological_network_feature_collection(feature_collection);
	}
}


void
GPlatesAppLogic::TopologyNetworkResolverLayerTask::modified_input_file(
		LayerInputChannelName::Type input_channel_name,
		const GPlatesModel::FeatureCollectionHandle::weak_ref &feature_collection)
{
	if (input_channel_name == LayerInputChannelName::TOPOLOGICAL_NETWORK_FEATURES)
	{
		// Let the reconstruct layer proxy know that one of the network feature collections has been modified.
		d_topology_network_resolver_layer_proxy
				->modified_topological_network_feature_collection(feature_collection);
	}
}


void
GPlatesAppLogic::TopologyNetworkResolverLayerTask::add_input_layer_proxy_connection(
		LayerInputChannelName::Type input_channel_name,
		const LayerProxy::non_null_ptr_type &layer_proxy)
{
	if (input_channel_name == LayerInputChannelName::TOPOLOGICAL_SECTION_LAYERS)
	{
		// The input layer proxy is one of the following layer proxy types:
		// - reconstruct,
		// - topological geometry resolver.

		boost::optional<ReconstructLayerProxy *> reconstruct_layer_proxy =
				LayerProxyUtils::get_layer_proxy_derived_type<ReconstructLayerProxy>(layer_proxy);
		if (reconstruct_layer_proxy)
		{
			d_current_reconstructed_geometry_topological_sections_layer_proxies.push_back(
					reconstruct_layer_proxy.get());
		}

		boost::optional<TopologyGeometryResolverLayerProxy *> topological_line_resolver_layer_proxy =
				LayerProxyUtils::get_layer_proxy_derived_type<TopologyGeometryResolverLayerProxy>(layer_proxy);
		if (topological_line_resolver_layer_proxy)
		{
			d_current_resolved_line_topological_sections_layer_proxies.push_back(
					topological_line_resolver_layer_proxy.get());
		}
	}
}


void
GPlatesAppLogic::TopologyNetworkResolverLayerTask::remove_input_layer_proxy_connection(
		LayerInputChannelName::Type input_channel_name,
				const LayerProxy::non_null_ptr_type &layer_proxy)
{
	if (input_channel_name == LayerInputChannelName::TOPOLOGICAL_SECTION_LAYERS)
	{
		// The input layer proxy is one of the following layer proxy types:
		// - reconstruct,
		// - topological geometry resolver.

		boost::optional<ReconstructLayerProxy *> reconstruct_layer_proxy =
				LayerProxyUtils::get_layer_proxy_derived_type<ReconstructLayerProxy>(layer_proxy);
		if (reconstruct_layer_proxy)
		{
			d_current_reconstructed_geometry_topological_sections_layer_proxies.erase(
					std::remove(
							d_current_reconstructed_geometry_topological_sections_layer_proxies.begin(),
							d_current_reconstructed_geometry_topological_sections_layer_proxies.end(),
							reconstruct_layer_proxy.get()),
					d_current_reconstructed_geometry_topological_sections_layer_proxies.end());
		}

		boost::optional<TopologyGeometryResolverLayerProxy *> topological_line_resolver_layer_proxy =
				LayerProxyUtils::get_layer_proxy_derived_type<TopologyGeometryResolverLayerProxy>(layer_proxy);
		if (topological_line_resolver_layer_proxy)
		{
			d_current_resolved_line_topological_sections_layer_proxies.erase(
					std::remove(
							d_current_resolved_line_topological_sections_layer_proxies.begin(),
							d_current_resolved_line_topological_sections_layer_proxies.end(),
							topological_line_resolver_layer_proxy.get()),
					d_current_resolved_line_topological_sections_layer_proxies.end());
		}
	}
}


void
GPlatesAppLogic::TopologyNetworkResolverLayerTask::update(
		const Reconstruction::non_null_ptr_type &reconstruction)
{
	d_topology_network_resolver_layer_proxy->set_current_reconstruction_time(reconstruction->get_reconstruction_time());

	// Get the 'reconstructed geometry' topological section layers.
	std::vector<ReconstructLayerProxy::non_null_ptr_type>
			reconstructed_geometry_topological_sections_layer_proxies;
	get_reconstructed_geometry_topological_sections_layer_proxies(
			reconstructed_geometry_topological_sections_layer_proxies,
			reconstruction);

	// Get the 'resolved line' topological section layers.
	std::vector<TopologyGeometryResolverLayerProxy::non_null_ptr_type>
			resolved_line_topological_sections_layer_proxies;
	get_resolved_line_topological_sections_layer_proxies(
			resolved_line_topological_sections_layer_proxies,
			reconstruction);

	// Notify our layer proxy of the topological sections layer proxies.
	d_topology_network_resolver_layer_proxy->set_current_topological_sections_layer_proxies(
			reconstructed_geometry_topological_sections_layer_proxies,
			resolved_line_topological_sections_layer_proxies);
}


bool
GPlatesAppLogic::TopologyNetworkResolverLayerTask::connected_to_topological_section_layers() const
{
	// If any topological section layers are connected...
	return !d_current_reconstructed_geometry_topological_sections_layer_proxies.empty() ||
		!d_current_resolved_line_topological_sections_layer_proxies.empty();
}


void
GPlatesAppLogic::TopologyNetworkResolverLayerTask::get_reconstructed_geometry_topological_sections_layer_proxies(
		std::vector<ReconstructLayerProxy::non_null_ptr_type> &reconstructed_geometry_topological_sections_layer_proxies,
		const Reconstruction::non_null_ptr_type &reconstruction) const
{
	if (connected_to_topological_section_layers())
	{
		// Restrict the topological section layers to only those that are currently connected.
		reconstructed_geometry_topological_sections_layer_proxies =
				d_current_reconstructed_geometry_topological_sections_layer_proxies;
	}
	else
	{
		// Find those layer outputs that come from a reconstruct layer.
		// These will be our topological sections layer proxies that generate reconstructed static geometries.
		// NOTE: We reference all active reconstruct layers because we don't know which ones contain
		// the topological sections that our topologies are referencing (it's a global lookup).
		reconstruction->get_active_layer_outputs<ReconstructLayerProxy>(
				reconstructed_geometry_topological_sections_layer_proxies);
	}
}


void
GPlatesAppLogic::TopologyNetworkResolverLayerTask::get_resolved_line_topological_sections_layer_proxies(
		std::vector<TopologyGeometryResolverLayerProxy::non_null_ptr_type> &resolved_line_topological_sections_layer_proxies,
		const Reconstruction::non_null_ptr_type &reconstruction) const
{
	if (connected_to_topological_section_layers())
	{
		// Restrict the topological section layers to only those that are currently connected.
		resolved_line_topological_sections_layer_proxies =
				d_current_resolved_line_topological_sections_layer_proxies;
	}
	else
	{
		// Find those layer outputs that come from a topological geometry layer.
		// These will be our topological sections layer proxies that generate resolved topological *lines*.
		// NOTE: We reference all active topological geometry layers because we don't know which ones contain
		// the topological sections that our topologies are referencing (it's a global lookup).
		reconstruction->get_active_layer_outputs<TopologyGeometryResolverLayerProxy>(
				resolved_line_topological_sections_layer_proxies);
	}
}


void
GPlatesAppLogic::TopologyNetworkResolverLayerTask::handle_topology_network_params_modified(
		TopologyNetworkLayerParams &layer_params)
{
	// Update our velocity layer proxy.
	d_topology_network_resolver_layer_proxy->set_current_topology_network_params(layer_params.get_topology_network_params());
}
