/*======================================================================================

	Program:		Range Imaging Toolkit (RITK)
	Module:			ritkAcquisitionPipeline.cxx
	Version:		$Revision: 2270 $
	Date:			$Date: 2011-12-23 10:23:18 +0100 (Fr, 23 Dez 2011) $

	Copyright (c) 2010-2011
	Pattern Recognition Lab, 
    University Erlangen-Nuremberg, Germany
    All rights reserved.

	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 RITKCopyright.txt for more information.

	http://www5.informatik.uni-erlangen.de/research/software/ritk

========================================================================================*/

#include "ritkAcquisitionPipeline.h"
#include "ritkDebugManager.h"


#include "ritkGenericImportImageContainer.h"

#include <QMetaType>

#include <itkImageFileWriter.h>
#include <itkRescaleIntensityImageFilter.h>



namespace ritk
{


//----------------------------------------------------------------------------
AcquisitionPipeline::AcquisitionPipeline()
{
	// Initialize pipeline members
	m_Source = 0;
	m_PipelineDirtyFlag = true;
	m_FrameGrabber = FrameGrabber::New();

	// Average of 50 update cycles
	SetNumberOfPassesToAverageRuntime(50);

	// Register the NewFrameEvent such that it can be used within the signal/slot mechanism
	qRegisterMetaType<NewFrameEvent::Pointer>("NewFrameEvent::Pointer");
}


//----------------------------------------------------------------------------
AcquisitionPipeline::~AcquisitionPipeline()
{
}


//----------------------------------------------------------------------------
void AcquisitionPipeline::Lock()
{
	m_Mutex.lock();
}


//----------------------------------------------------------------------------
void AcquisitionPipeline::Unlock()
{
	m_Mutex.unlock();
}


//----------------------------------------------------------------------------
void AcquisitionPipeline::SetSource(RImageSourcePointer Source)
{
	QMutexLocker locker(&m_Mutex);
	
	// Set the source
	m_Source = Source;

	// Indicate that the pipeline changed and initialize the pipeline
	// In case the source is removed (by passing NULL as argument) this
	// will also clean the pipeline.
	m_PipelineDirtyFlag = true;
	InitializePipeline();
}


//----------------------------------------------------------------------------
AcquisitionPipeline::RImageSourceConstPointer AcquisitionPipeline::GetSource()
{
	QMutexLocker locker(&m_Mutex);
	
	AcquisitionPipeline::RImageSourceConstPointer Source = ((const ritk::RImageSourceF2*) m_Source);
	return Source;
}


//----------------------------------------------------------------------------
void AcquisitionPipeline::AddFilter(RImageFilterPointer Filter)
{
	QMutexLocker locker(&m_Mutex);

	// Add the filter to the internal list
	m_Filters.append(Filter);
	
	// Indicate that we changed the pipeline
	m_PipelineDirtyFlag = true;
}


//----------------------------------------------------------------------------
bool AcquisitionPipeline::RemoveFilter(RImageFilterPointer Filter)
{
	QMutexLocker locker(&m_Mutex);

	// Iterate through the filter container
	for ( QList<RImageFilterPointer>::Iterator it = m_Filters.begin(); it != m_Filters.end(); it++ )
	{
		// Found the correct filter
		if ( *it == Filter )
		{
			// Save the filter which allows us to clean the pipeline
			RImageFilterPointer tmp = *it;
			tmp->InvalidateOutputs();

			// Remove the filter from the list
			(*it) = NULL;
			m_Filters.erase(it);
			
			// Re-initialize the pipeline. This ensures that all data objects are connected properly
			m_PipelineDirtyFlag = true;
			InitializePipeline();

			return true;
		}
	}
	return false;
}


//----------------------------------------------------------------------------
void 
AcquisitionPipeline::ResetPipeline()
{
	Lock();
	m_PipelineDirtyFlag = true;
	Unlock();
}


//----------------------------------------------------------------------------
void
AcquisitionPipeline::ExecuteOnce()
{
	ExecutePipelineEvent::Pointer EPE = ExecutePipelineEvent::Pointer(new ExecutePipelineEvent());
	this->EnqueueEvent(EPE);
}


//----------------------------------------------------------------------------
void
AcquisitionPipeline::SetRequestedRegion(RImageType::RegionType Region)
{
	Lock();
	m_FrameGrabber->GetOutput()->SetRequestedRegion(Region);
	Unlock();
}


//----------------------------------------------------------------------------
void
AcquisitionPipeline::ProcessEvent(Event::Pointer Event)
{
	// Check if we can handle this event
	if ( Event->type() == StopEvent::EventType )
	{
		m_FrameGrabber = NULL;
		m_Source = NULL;
	}
	else if ( Event->type() == ExecutePipelineEvent::EventType )
	{
		QMutexLocker locker(&m_Mutex);

		StartTimer();
	
		// Update the pipeline
		RImagePointer PipelineOutput = NULL;
		//try
		{
			//m_PipelineDirtyFlag = true;
			UpdatePipeline();
			PipelineOutput = m_FrameGrabber->GetOutput();
		}
		/*catch ( itk::ExceptionObject &e )
		{
			std::cout << e << std::endl;
			LOG_DEB(e);
			m_FrameGrabber->ResetPipeline();
			m_PipelineDirtyFlag = true;
			return;
		}
		catch ( std::exception &e )
		{
			LOG_DEB(e.what());
			throw e;
		}*/

		// Sanity check
		if ( !PipelineOutput )
		{
			LOG_DEB("Pipeline update failed...");
			return;
		}

		// Emit the new frame signal. Do not forget to disconnect the frame from the pipeline
		//PipelineOutput->DisconnectPipeline();
		NewFrameEvent::Pointer e = NewFrameEvent::Pointer(new NewFrameEvent());
		e->RImage = PipelineOutput;

		StopTimer();

		//std::cout << PipelineOutput << std::endl;

		emit NewFrame(e);
		emit NewFrame();
	}
	else
	{
		// Let the base class handle this event
		ThreadedWorker::ProcessEvent(Event);
	}
}


//----------------------------------------------------------------------------
void 
AcquisitionPipeline::InitializePipeline()
{
	// Nothing to do
	if ( !m_PipelineDirtyFlag )
		return;

	// Disconnect outputs and reset the pipeline
	RImagePointer tmp = NULL;
	if ( m_Source )
	{
		tmp = m_Source->GetOutput();
	}
	for ( int i = 0; i < m_Filters.size(); i++ )
	{
		RImageFilterPointer tmpFilter = m_Filters[i];
		m_Filters[i]->SetInput(tmp);
		// invalidating an output creates a new blank output
		m_Filters[i]->InvalidateOutputs();
		tmp = m_Filters[i]->GetOutput();
		tmpFilter = m_Filters[i];
	}

	m_FrameGrabber = FrameGrabber::New();

	// Pipeline is now initialized
	m_PipelineDirtyFlag = false;
}


//----------------------------------------------------------------------------
void
AcquisitionPipeline::UpdatePipeline()
{
	// Sanity check
	if ( !m_Source )
		return;

	// Initialize the pipeline 
	InitializePipeline();

	// Set the input to the memory relocator and perform an update. 
	// Setting the correct input each time is necessary as some object might 
	// have disconnected the output from the pipeline (note that the initialize 
	// method called above does not account for this issue due to performance reasons).
	if ( m_Filters.size() == 0 )
		m_FrameGrabber->SetInput(m_Source->GetOutput());
	else
		m_FrameGrabber->SetInput(m_Filters[m_Filters.size()-1]->GetOutput());

	// Update the pipeline and grab the frame
	m_FrameGrabber->UpdateLargestPossibleRegion();
	
	// Finally compute the world coord image
	m_FrameGrabber->GetOutput()->Finish();

	// Check for a generic container
	typedef GenericImportImageContainer<RImageType::WorldCoordImageType::PixelContainer::ElementIdentifier, RImageType::WorldCoordImageType::PixelContainer::Element> ContainerType;
	ContainerType::Pointer Container = dynamic_cast<ContainerType*>(m_FrameGrabber->GetOutput()->GetWorldCoordImage()->GetPixelContainer());
	if ( Container )
	{
		Container->Synchronize();
	}

	/*double t1, t2;
	m_FrameGrabber->GetElapsedTime(&t1,&t2);
	std::cout << "m_FrameGrabber: " << t1 << " (" << t2 << ")" << std::endl;*/

	//m_FrameGrabber->GetOutput()->SynchronizeGenericImportImageContainers();
}




// ==================== //
// ExecutePipelineEvent //
// ==================== //
QEvent::Type ExecutePipelineEvent::EventType = static_cast<QEvent::Type>(QEvent::registerEventType());

ExecutePipelineEvent::ExecutePipelineEvent() :
	Event(EventType)
{
}

ExecutePipelineEvent::~ExecutePipelineEvent()
{
}


} // end namespace ritk
