/*
-----------------------------------------------------------------------
Copyright: 2010-2014, iMinds-Vision Lab, University of Antwerp
                2014, CWI, Amsterdam
Contact: astra@uantwerpen.be
Website: http://sf.net/projects/astra-toolbox
This file is part of the ASTRA Toolbox.
The ASTRA Toolbox 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 3 of the License, or
(at your option) any later version.
The ASTRA Toolbox 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 the ASTRA Toolbox. If not, see .
-----------------------------------------------------------------------
$Id$
*/
#ifndef _INC_ASTRA_DATAPROJECTOR
#define _INC_ASTRA_DATAPROJECTOR
#include "Projector2D.h"
#include "TypeList.h"
#include "ProjectorTypelist.h"
#include "DataProjectorPolicies.h"
namespace astra
{
/**
 * Interface class for the Data Projector. The sole purpose of this class is to force child classes to implement a series of methods
 */
class CDataProjectorInterface {
public:
	CDataProjectorInterface() { }
	virtual ~CDataProjectorInterface() { }
	virtual void project() = 0;
	virtual void projectSingleProjection(int _iProjection) = 0;
	virtual void projectSingleRay(int _iProjection, int _iDetector) = 0;
//	virtual void projectSingleVoxel(int _iRow, int _iCol) = 0;
//	virtual void projectAllVoxels() = 0;
};
/**
 * Templated Data Projector Class. In this class a specific projector and policies are combined.
 */
template 
class CDataProjector: public CDataProjectorInterface {
private:
	Projector* m_pProjector;
	Policy m_pPolicy;
public:
	CDataProjector() {};
	CDataProjector(Projector* _p, Policy _a);
	~CDataProjector();
	virtual void project();
	virtual void projectSingleProjection(int _iProjection);
	virtual void projectSingleRay(int _iProjection, int _iDetector);
//	virtual void projectSingleVoxel(int _iRow, int _iCol);
//	virtual void projectAllVoxels();
};
//----------------------------------------------------------------------------------------
/**
 * Constructor
*/
template 
CDataProjector::CDataProjector(Projector* _p, Policy _a) 
{ 
	m_pProjector = _p;
	m_pPolicy = _a; 
}
//----------------------------------------------------------------------------------------
/**
 * Destructor
*/
template 
CDataProjector::~CDataProjector() 
{ 
	// does nothing
}
//----------------------------------------------------------------------------------------
/**
 * Compute projection using the algorithm specific to the projector type
*/
template 
void CDataProjector::project() 
{ 
	m_pProjector->project(m_pPolicy);
}
//----------------------------------------------------------------------------------------
/**
 * Compute just one projection using the algorithm specific to the projector type
*/
template 
void CDataProjector::projectSingleProjection(int _iProjection) 
{ 
	m_pProjector->projectSingleProjection(_iProjection, m_pPolicy);
}
//----------------------------------------------------------------------------------------
/**
 * Compute projection of one ray using the algorithm specific to the projector type
*/
template 
void CDataProjector::projectSingleRay(int _iProjection, int _iDetector)
{ 
	m_pProjector->projectSingleRay(_iProjection, _iDetector, m_pPolicy);
}
//----------------------------------------------------------------------------------------
//template 
//void CDataProjector::projectSingleVoxel(int _iRow, int _iCol) 
//{ 
//	m_pProjector->projectSingleVoxel(_iRow, _iCol, m_pPolicy);
//}
//----------------------------------------------------------------------------------------
//template 
//void CDataProjector::projectAllVoxels() 
//{ 
//	m_pProjector->projectAllVoxels(m_pPolicy);
//}
//----------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------
// Create a new datainterface from the projector TypeList
namespace typelist {
	template  
	struct CreateDataProjector { 
		template 
		 static void find (U& functor, CProjector2D* _pProjector, const Policy& _pPolicy) {
			 if (functor(TList::Head::type)) {
				functor.res = new CDataProjector(static_cast(_pProjector), _pPolicy);
			 }
			 CreateDataProjector::find(functor, _pProjector, _pPolicy); 
		 }
	}; 
	template <> 
	struct CreateDataProjector {
		template  
		static void find(U& functor, CProjector2D* _pProjector, const Policy& _pPolicy) {}
	}; 
	struct functor_find_datainterface {
		functor_find_datainterface() { res = NULL; }
		bool operator() (std::string name) { 
			return strcmp(tofind.c_str(), name.c_str()) == 0;
		} 
		std::string tofind;
		CDataProjectorInterface* res;
	};
}
//-----------------------------------------------------------------------------------------
/**
 * Data Projector Dispatcher - 1 Policy
 */
template 
static CDataProjectorInterface* dispatchDataProjector(CProjector2D* _pProjector, const Policy& _policy)
{
	typelist::functor_find_datainterface finder = typelist::functor_find_datainterface();
	finder.tofind = _pProjector->getType();
	typelist::CreateDataProjector::find(finder, _pProjector, _policy);
	return finder.res;
}
/**
 * Data Projector Dispatcher - 2 Policies
 */
template 
static CDataProjectorInterface* dispatchDataProjector(CProjector2D* _pProjector, 
													  const Policy1& _policy,
													  const Policy2& _policy2,
													  bool _bUsePolicy1 = true, 
													  bool _bUsePolicy2 = true) 
{
	if (!_bUsePolicy1 && !_bUsePolicy2) {
		return dispatchDataProjector(_pProjector, EmptyPolicy());
	} else if (!_bUsePolicy1) {
		return dispatchDataProjector(_pProjector, _policy2);
	} else if (!_bUsePolicy2) {
		return dispatchDataProjector(_pProjector, _policy);
	} else {
		return dispatchDataProjector(_pProjector, CombinePolicy(_policy, _policy2));
	}
	
}
/**
 * Data Projector Dispatcher - 3 Policies
 */
template 
static CDataProjectorInterface* dispatchDataProjector(CProjector2D* _pProjector, 
													  const Policy1& _policy1,
													  const Policy2& _policy2,
													  const Policy3& _policy3,
													  bool _bUsePolicy1 = true, 
													  bool _bUsePolicy2 = true,
													  bool _bUsePolicy3 = true) 
{
	if (!_bUsePolicy1) {
		return dispatchDataProjector(_pProjector, _policy2, _policy3, _bUsePolicy2, _bUsePolicy3);
	} else if (!_bUsePolicy2) {
		return dispatchDataProjector(_pProjector, _policy1, _policy3, _bUsePolicy1, _bUsePolicy3);
	} else if (!_bUsePolicy3) {
		return dispatchDataProjector(_pProjector, _policy1, _policy2, _bUsePolicy1, _bUsePolicy2);
	} else {
		return dispatchDataProjector(_pProjector, Combine3Policy(_policy1, _policy2, _policy3));
	}
}
/**
 * Data Projector Dispatcher - 4 Policies
 */
template 
static CDataProjectorInterface* dispatchDataProjector(CProjector2D* _pProjector, 
													  const Policy1& _policy1,
													  const Policy2& _policy2,
													  const Policy3& _policy3,
													  const Policy4& _policy4,
													  bool _bUsePolicy1 = true, 
													  bool _bUsePolicy2 = true,
													  bool _bUsePolicy3 = true,
													  bool _bUsePolicy4 = true) 
{
	if (!_bUsePolicy1) {
		return dispatchDataProjector(_pProjector, _policy2, _policy3, _policy4, _bUsePolicy2, _bUsePolicy3, _bUsePolicy4);
	} else if (!_bUsePolicy2) {
		return dispatchDataProjector(_pProjector, _policy1, _policy3, _policy4, _bUsePolicy1, _bUsePolicy3, _bUsePolicy4);
	} else if (!_bUsePolicy3) {
		return dispatchDataProjector(_pProjector, _policy1, _policy2, _policy4, _bUsePolicy1, _bUsePolicy2, _bUsePolicy4);
	} else if (!_bUsePolicy4) {
		return dispatchDataProjector(_pProjector, _policy1, _policy2, _policy3, _bUsePolicy1, _bUsePolicy2, _bUsePolicy3);
	} else {
		return dispatchDataProjector(_pProjector, Combine4Policy(_policy1, _policy2, _policy3, _policy4));
	}
}
/**
 * Data Projector Dispatcher - 5 Policies
 */
template 
static CDataProjectorInterface* dispatchDataProjector(CProjector2D* _pProjector, 
													  const Policy1& _policy1,
													  const Policy2& _policy2,
													  const Policy3& _policy3,
													  const Policy4& _policy4,
													  const Policy5& _policy5,
													  bool _bUsePolicy1 = true, 
													  bool _bUsePolicy2 = true,
													  bool _bUsePolicy3 = true,
													  bool _bUsePolicy4 = true,
													  bool _bUsePolicy5 = true) 
{
	if (!_bUsePolicy1) {
		return dispatchDataProjector(_pProjector, _policy2, _policy3, _policy4, _policy5, _bUsePolicy2, _bUsePolicy3, _bUsePolicy4, _bUsePolicy5);
	} else if (!_bUsePolicy2) {
		return dispatchDataProjector(_pProjector, _policy1, _policy3, _policy4, _policy5, _bUsePolicy1, _bUsePolicy3, _bUsePolicy4, _bUsePolicy5);
	} else if (!_bUsePolicy3) {
		return dispatchDataProjector(_pProjector, _policy1, _policy2, _policy4, _policy5, _bUsePolicy1, _bUsePolicy2, _bUsePolicy4, _bUsePolicy5);
	} else if (!_bUsePolicy4) {
		return dispatchDataProjector(_pProjector, _policy1, _policy2, _policy3, _policy5, _bUsePolicy1, _bUsePolicy2, _bUsePolicy3, _bUsePolicy5);
	} else if (!_bUsePolicy5) {
		return dispatchDataProjector(_pProjector, _policy1, _policy2, _policy3, _policy4, _bUsePolicy1, _bUsePolicy2, _bUsePolicy3, _bUsePolicy4);
	} else {
		return dispatchDataProjector(_pProjector, CombinePolicy< Combine4Policy, Policy5>(
														Combine4Policy(_policy1, _policy2, _policy3, _policy4), 
														_policy5)
									);
	}
}
//-----------------------------------------------------------------------------------------
/**
 * Data Projector Project
 */
template 
static void projectData(CProjector2D* _pProjector, const Policy& _policy)
{
	CDataProjectorInterface* dp = dispatchDataProjector(_pProjector, _policy);
	dp->project();
	delete dp;
}
} // namespace astra
#endif