/** astra_mex_data2d('change_geometry', id, geom);
 *
 * Change the associated geometry of a 2d data object (volume or sinogram)
 * id: identifier of the 2d data object as stored in the astra-library.
 * geom: the new geometry struct, as created by astra_create_vol/proj_geom
 */
void astra_mex_data2d_change_geometry(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[])
{ 
	// step1: check input
	if (nrhs < 3) {
		mexErrMsgTxt("Not enough arguments.  See the help document for a detailed argument list. \n");
		return;
	}
	if (!mxIsDouble(prhs[1])) {
		mexErrMsgTxt("Identifier should be a scalar value. \n");
		return;
	}

	// step2: get data object
	int iDataID = (int)(mxGetScalar(prhs[1]));
	CFloat32Data2D* pDataObject = astra::CData2DManager::getSingleton().get(iDataID);
	if (!pDataObject || !pDataObject->isInitialized()) {
		mexErrMsgTxt("Data object not found or not initialized properly.\n");
		return;
	}

	CFloat32ProjectionData2D* pSinogram = dynamic_cast<CFloat32ProjectionData2D*>(pDataObject);

	if (pSinogram) {
		// Projection data

		// Read geometry
		if (!mxIsStruct(prhs[2])) {
			mexErrMsgTxt("Argument 3 is not a valid MATLAB struct.\n");
		}
		XMLDocument* xml = struct2XML("ProjectionGeometry", prhs[2]);
		Config cfg;
		cfg.self = xml->getRootNode();
		// FIXME: Change how the base class is created. (This is duplicated
		// in 'create' and Projector2D.cpp.)
		std::string type = cfg.self->getAttribute("type");
		CProjectionGeometry2D* pGeometry;
		if (type == "sparse_matrix") {
			pGeometry = new CSparseMatrixProjectionGeometry2D();
		} else if (type == "fanflat") {
			//CFanFlatProjectionGeometry2D* pFanFlatProjectionGeometry = new CFanFlatProjectionGeometry2D();
			//pFanFlatProjectionGeometry->initialize(Config(node));
			//m_pProjectionGeometry = pFanFlatProjectionGeometry;
			pGeometry = new CFanFlatProjectionGeometry2D();	
		} else if (type == "fanflat_vec") {
			pGeometry = new CFanFlatVecProjectionGeometry2D();	
		} else {
			pGeometry = new CParallelProjectionGeometry2D();	
		}
		if (!pGeometry->initialize(cfg)) {
			mexErrMsgTxt("Geometry class not initialized. \n");
			delete pGeometry;
			delete xml;
			return;
		}
		// If data is specified, check dimensions
		if (pGeometry->getDetectorCount() != pSinogram->getDetectorCount() || pGeometry->getProjectionAngleCount() != pSinogram->getAngleCount()) {
			mexErrMsgTxt("The dimensions of the data do not match those specified in the geometry. \n");
			delete pGeometry;
			delete xml;
			return;
		}

		// If ok, change geometry
		pSinogram->changeGeometry(pGeometry);
		delete pGeometry;
		delete xml;

		return;
	}

	CFloat32VolumeData2D* pVolume = dynamic_cast<CFloat32VolumeData2D*>(pDataObject);

	if (pVolume) {
		// Volume data

		// Read geometry
		if (!mxIsStruct(prhs[2])) {
			mexErrMsgTxt("Argument 3 is not a valid MATLAB struct.\n");
		}
		XMLDocument* xml = struct2XML(string("VolumeGeometry"), prhs[2]);
		Config cfg;
		cfg.self = xml->getRootNode();
		CVolumeGeometry2D* pGeometry = new CVolumeGeometry2D();
		if (!pGeometry->initialize(cfg)) {
			mexErrMsgTxt("Geometry class not initialized. \n");
			delete xml;
			delete pGeometry;
			return;
		}
		// If data is specified, check dimensions
		if (pGeometry->getGridColCount() != pVolume->getWidth() || pGeometry->getGridRowCount() != pVolume->getHeight()) {
			mexErrMsgTxt("The dimensions of the data do not match those specified in the geometry. \n");
			delete xml;
			delete pGeometry;
			return;
		}

		// If ok, change geometry
		pVolume->changeGeometry(pGeometry);
		delete xml;
		delete pGeometry;

	}

	mexErrMsgTxt("Data object not found or not initialized properly.\n");
	return;
}
/** id = astra_mex_data2d('create', datatype, geometry, data);
 *   
 * Create a new data 2d object in the astra-library.
 * type: '-vol' for volume data, '-sino' for projection data
 * geom: MATLAB struct with the geometry for the data
 * data: Optional. Can be either a MATLAB matrix containing the data. In that case the dimensions 
 * should match that of the geometry of the object.  It can also be a single value, in which case 
 * the entire data will be set to that value.  If this isn't specified all values are set to 0.
 * id: identifier of the 2d data object as it is now stored in the astra-library.
 */
void astra_mex_data2d_create(int& nlhs, mxArray* plhs[], int& nrhs, const mxArray* prhs[])
{ 
	// step1: get datatype
	if (nrhs < 3) {
		mexErrMsgTxt("Not enough arguments.  See the help document for a detailed argument list. \n");
		return;
	}

	string sDataType = mex_util_get_string(prhs[1]);	
	CFloat32Data2D* pDataObject2D = NULL;

	if (nrhs >= 4 && !(mex_is_scalar(prhs[3])|| mxIsDouble(prhs[3]) || mxIsLogical(prhs[3]) || mxIsSingle(prhs[3]) )) {
		mexErrMsgTxt("Data must be single, double or logical.");
		return;
	}
	if (mxIsSparse(prhs[2])) {
		mexErrMsgTxt("Data may not be sparse.");
		return;
	}

	// SWITCH DataType
	if (sDataType == "-vol") {
		// Read geometry
		if (!mxIsStruct(prhs[2])) {
			mexErrMsgTxt("Argument 3 is not a valid MATLAB struct.\n");
		}
		XMLDocument* xml = struct2XML(string("VolumeGeometry"), prhs[2]);
		if (!xml)
			return;
		Config cfg;
		cfg.self = xml->getRootNode();
		CVolumeGeometry2D* pGeometry = new CVolumeGeometry2D();
		if (!pGeometry->initialize(cfg)) {
			mexErrMsgTxt("Geometry class not initialized. \n");
			delete xml;
			delete pGeometry;
			return;
		}
		// If data is specified, check dimensions
		if (nrhs >= 4 && !mex_is_scalar(prhs[3])) {
			if (pGeometry->getGridColCount() != mxGetN(prhs[3]) || pGeometry->getGridRowCount() != mxGetM(prhs[3])) {
				mexErrMsgTxt("The dimensions of the data do not match those specified in the geometry. \n");
				delete xml;
				delete pGeometry;
				return;
			}
		}
		// Initialize data object
		pDataObject2D = new CFloat32VolumeData2D(pGeometry);		
		delete pGeometry;
		delete xml;
	}
	else if (sDataType == "-sino") {
		// Read geometry
		if (!mxIsStruct(prhs[2])) {
			mexErrMsgTxt("Argument 3 is not a valid MATLAB struct.\n");
		}
		XMLDocument* xml = struct2XML("ProjectionGeometry", prhs[2]);
		if (!xml)
			return;
		Config cfg;
		cfg.self = xml->getRootNode();
		// FIXME: Change how the base class is created. (This is duplicated
		// in 'change_geometry' and Projector2D.cpp.)
		std::string type = cfg.self->getAttribute("type");
		CProjectionGeometry2D* pGeometry;
		if (type == "sparse_matrix") {
			pGeometry = new CSparseMatrixProjectionGeometry2D();
		} else if (type == "fanflat") {
			//CFanFlatProjectionGeometry2D* pFanFlatProjectionGeometry = new CFanFlatProjectionGeometry2D();
			//pFanFlatProjectionGeometry->initialize(Config(node));
			//m_pProjectionGeometry = pFanFlatProjectionGeometry;
			pGeometry = new CFanFlatProjectionGeometry2D();	
		} else if (type == "fanflat_vec") {
			pGeometry = new CFanFlatVecProjectionGeometry2D();	
		} else {
			pGeometry = new CParallelProjectionGeometry2D();	
		}
		if (!pGeometry->initialize(cfg)) {
			mexErrMsgTxt("Geometry class not initialized. \n");
			delete pGeometry;
			delete xml;
			return;
		}
		// If data is specified, check dimensions
		if (nrhs >= 4 && !mex_is_scalar(prhs[3])) {
			if (pGeometry->getDetectorCount() != mxGetN(prhs[3]) || pGeometry->getProjectionAngleCount() != mxGetM(prhs[3])) {
				mexErrMsgTxt("The dimensions of the data do not match those specified in the geometry. \n");
				delete pGeometry;
				delete xml;
				return;
			}
		}
		// Initialize data object
		pDataObject2D = new CFloat32ProjectionData2D(pGeometry);
		delete pGeometry;
		delete xml;
	}
	else {
		mexErrMsgTxt("Invalid datatype.  Please specify '-vol' or '-sino'. \n");
		return;
	}

	// Check initialization
	if (!pDataObject2D->isInitialized()) {
		mexErrMsgTxt("Couldn't initialize data object.\n");
		delete pDataObject2D;
		return;
	}

	// Store data
	if (nrhs == 3) {
		for (int i = 0; i < pDataObject2D->getSize(); ++i) {
			pDataObject2D->getData()[i] = 0.0f;
		}
	}

	// Store data
	if (nrhs >= 4) {
		// fill with scalar value
		if (mex_is_scalar(prhs[3])) {
			float32 fValue = (float32)mxGetScalar(prhs[3]);
			for (int i = 0; i < pDataObject2D->getSize(); ++i) {
				pDataObject2D->getData()[i] = fValue;
			}
		}
		// fill with array value
		else {
			const mwSize* dims = mxGetDimensions(prhs[3]);
			// Check Data dimensions
			if (pDataObject2D->getWidth() != mxGetN(prhs[3]) || pDataObject2D->getHeight() != mxGetM(prhs[3])) {
				mexErrMsgTxt("The dimensions of the data do not match those specified in the geometry. \n");
				return;
			}

			// logical data		
			if (mxIsLogical(prhs[3])) {
				bool* pbMatlabData = mxGetLogicals(prhs[3]);
				int i = 0;
				int col, row;
				for (col = 0; col < dims[1]; ++col) {
					for (row = 0; row < dims[0]; ++row) {
						pDataObject2D->getData2D()[row][col] = (float32)pbMatlabData[i];
						++i;
					}
				}
			// double data
			} else if (mxIsDouble(prhs[3])) {
				double* pdMatlabData = mxGetPr(prhs[3]);
				int i = 0;
				int col, row;
				for (col = 0; col < dims[1]; ++col) {
					for (row = 0; row < dims[0]; ++row) {
						pDataObject2D->getData2D()[row][col] = pdMatlabData[i];
						++i;
					}
				}
			// single data
			} else if (mxIsSingle(prhs[3])) {
				const float* pfMatlabData = (const float *)mxGetData(prhs[3]);
				int i = 0;
				int col, row;
				for (col = 0; col < dims[1]; ++col) {
					for (row = 0; row < dims[0]; ++row) {
						pDataObject2D->getData2D()[row][col] = pfMatlabData[i];
						++i;
					}
				}
			} else {
				ASTRA_ASSERT(false);
			}
		}
	}

	// step4: store data object
	int iIndex = CData2DManager::getSingleton().store(pDataObject2D);

	// step5: return data id
	if (1 <= nlhs) {
		plhs[0] = mxCreateDoubleScalar(iIndex);
	}

}
//----------------------------------------------------------------------------------------
// Run
void CCudaForwardProjectionAlgorithm::run(int)
{
	// check initialized
	assert(m_bIsInitialized);

	CVolumeGeometry2D* pVolGeom = m_pVolume->getGeometry();
	const CParallelProjectionGeometry2D* parProjGeom = dynamic_cast<CParallelProjectionGeometry2D*>(m_pSinogram->getGeometry());
	const CFanFlatProjectionGeometry2D* fanProjGeom = dynamic_cast<CFanFlatProjectionGeometry2D*>(m_pSinogram->getGeometry());
	const CFanFlatVecProjectionGeometry2D* fanVecProjGeom = dynamic_cast<CFanFlatVecProjectionGeometry2D*>(m_pSinogram->getGeometry());

	bool ok = false;
	if (parProjGeom) {

		float *offsets, *angles, detSize, outputScale;
		ok = convertAstraGeometry(pVolGeom, parProjGeom, offsets, angles, detSize, outputScale);

		ASTRA_ASSERT(ok); // FIXME

		// FIXME: Output scaling

		ok = astraCudaFP(m_pVolume->getDataConst(), m_pSinogram->getData(),
		                 pVolGeom->getGridColCount(), pVolGeom->getGridRowCount(),
		                 parProjGeom->getProjectionAngleCount(),
		                 parProjGeom->getDetectorCount(),
		                 angles, offsets, detSize,
		                 m_iDetectorSuperSampling, 1.0f * outputScale, m_iGPUIndex);

		delete[] offsets;
		delete[] angles;

	} else if (fanProjGeom || fanVecProjGeom) {

		astraCUDA::SFanProjection* projs;
		float outputScale;

		if (fanProjGeom) {
			ok = convertAstraGeometry(pVolGeom, fanProjGeom, projs, outputScale);
		} else {
			ok = convertAstraGeometry(pVolGeom, fanVecProjGeom, projs, outputScale);
		}

		ASTRA_ASSERT(ok);

		ok = astraCudaFanFP(m_pVolume->getDataConst(), m_pSinogram->getData(),
		                    pVolGeom->getGridColCount(), pVolGeom->getGridRowCount(),
		                    m_pSinogram->getGeometry()->getProjectionAngleCount(),
		                    m_pSinogram->getGeometry()->getDetectorCount(),
		                    projs,
		                    m_iDetectorSuperSampling, outputScale, m_iGPUIndex);

		delete[] projs;

	} else {

		ASTRA_ASSERT(false);

	}

	ASTRA_ASSERT(ok);

}