//##########################################################################
//#                                                                        #
//#                              CLOUDCOMPARE                              #
//#                                                                        #
//#  This program 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; version 2 or later of the License.      #
//#                                                                        #
//#  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 the          #
//#  GNU General Public License for more details.                          #
//#                                                                        #
//#          COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI)             #
//#                                                                        #
//##########################################################################

#include "IcmFilter.h"

//qCC_db
#include <ccCameraSensor.h>
#include <ccImage.h>
#include <ccLog.h>

//Qt
#include <QFileInfo>

//system
#include <cassert>
#include <cstdio>

//Max number of characters per line in an ASCII file
//TODO: use QFile instead!
const int MAX_ASCII_FILE_LINE_LENGTH = 4096;


IcmFilter::IcmFilter()
	: FileIOFilter({
					"_ICM Filter",
					DEFAULT_PRIORITY,	// priority
					QStringList{ "icm" },
					"icm",
					QStringList{ "Clouds + calibrated images [meta][ascii] (*.icm)" },
					QStringList(),
					Import
		})
{
}

CC_FILE_ERROR IcmFilter::loadFile(const QString& filename, ccHObject& container, LoadParameters& parameters)
{
	if (CheckForSpecialChars(filename))
	{
		ccLog::Warning(QString("[ICM] Input filename contains special characters. It might be rejected by the I/O filter..."));
	}

	//ouverture du fichier
#ifdef _MSC_VER
	FILE* fp = _wfopen(filename.toStdWString().c_str(), L"rt");
#else
	FILE* fp = fopen(qPrintable(filename), "rt");
#endif
	if (!fp)
	{
		return CC_FERR_READING;
	}

	//buffer
	char line[MAX_ASCII_FILE_LINE_LENGTH];

	//lecture du header
	if (!fgets(line, MAX_ASCII_FILE_LINE_LENGTH, fp))
	{
		fclose(fp);
		return CC_FERR_READING;
	}

	if (strncmp(line, "#CC_ICM_FILE", 12) != 0)
	{
		fclose(fp);
		return CC_FERR_WRONG_FILE_TYPE;
	}

	//on extrait le chemin relatif
	QString path = QFileInfo(filename).absolutePath();

	char cloudFileName[MAX_ASCII_FILE_LINE_LENGTH];
	if (!fgets(line, MAX_ASCII_FILE_LINE_LENGTH, fp))
	{
		fclose(fp);
		return CC_FERR_READING;
	}
	if (strncmp(line, "FILE_NAME=", 10) != 0)
	{
		fclose(fp);
		return CC_FERR_WRONG_FILE_TYPE;
	}
	sscanf(line, "FILE_NAME=%s", cloudFileName);

	char subFileType[12];
	if (!fgets(line, MAX_ASCII_FILE_LINE_LENGTH, fp))
	{
		fclose(fp);
		return CC_FERR_READING;
	}
	if (strncmp(line, "FILE_TYPE=", 10) != 0)
	{
		fclose(fp);
		return CC_FERR_WRONG_FILE_TYPE;
	}
	sscanf(line, "FILE_TYPE=%s", subFileType);

	FileIOFilter::Shared filter = FileIOFilter::FindBestFilterForExtension(subFileType);
	if (!filter)
	{
		ccLog::Warning(QString("[ICM] No I/O filter found for loading file '%1' (type = '%2')").arg(cloudFileName, subFileType));
		fclose(fp);
		return CC_FERR_UNKNOWN_FILE;
	}

	//load the corresponding file (potentially containing several clouds)
	CC_FILE_ERROR result = CC_FERR_NO_ERROR;
	ccHObject* entities = FileIOFilter::LoadFromFile(QString("%1/%2").arg(path, cloudFileName), parameters, filter, result);
	if (!entities)
	{
		fclose(fp);
		return CC_FERR_READING;
	}

	container.addChild(entities);

	//chargement des images
	if (!fgets(line, MAX_ASCII_FILE_LINE_LENGTH, fp))
	{
		ccLog::Error("[ICM] Read error (IMAGES_DESCRIPTOR)! No image loaded");
		fclose(fp);
		return CC_FERR_READING;
	}
	else
	{
		if (strncmp(line, "IMAGES_DESCRIPTOR=", 18) != 0)
		{
			fclose(fp);
			return CC_FERR_WRONG_FILE_TYPE;
		}
		char imagesDescriptorFileName[MAX_ASCII_FILE_LINE_LENGTH];
		sscanf(line, "IMAGES_DESCRIPTOR=%s", imagesDescriptorFileName);

		int n = LoadCalibratedImages(entities, path, imagesDescriptorFileName, entities->getBB_recursive());
		ccLog::Print("[ICM] %i image(s) loaded ...", n);
	}

	fclose(fp);
	return CC_FERR_NO_ERROR;
}

int IcmFilter::LoadCalibratedImages(ccHObject* entities, const QString& path, const QString& imageDescFilename, const ccBBox& globalBBox)
{
	assert(entities);

	//ouverture du fichier
	QString completeImageDescFilename = QString("%1/%2").arg(path, imageDescFilename);

	if (CheckForSpecialChars(completeImageDescFilename))
	{
		ccLog::Warning(QString("[ICM] File '%1' contains special characters. It might be rejected by the I/O filter...").arg(completeImageDescFilename));
	}

#ifdef _MSC_VER
	FILE* fp = _wfopen(completeImageDescFilename.toStdWString().c_str(), L"rt");
#else
	FILE* fp = fopen(qPrintable(completeImageDescFilename), "rt");
#endif
	if (fp == nullptr)
	{
		ccLog::Error(QString("[IcmFilter::loadCalibratedImages] Error opening file %1!").arg(completeImageDescFilename));
		return -1;
	}

	//buffers
	char line[MAX_ASCII_FILE_LINE_LENGTH];
#ifdef INCLUDE_PHOTOS
	char totalFileName[256];
#endif
	int loadedImages = 0;

	//IL FAUDRAIT ETRE PLUS SOUPLE QUE CA !!!
	while (fgets(line, MAX_ASCII_FILE_LINE_LENGTH, fp) != nullptr)
	{
		if (line[0] == 'D' && line[1] == 'E' && line[2] == 'F')
		{
			char imageFileName[256];
			sscanf(line, "DEF %s Viewpoint {", imageFileName);

			//add absolute path
			ccImage* CI = new ccImage();
			QString errorStr;
			if (!CI->load(QString("%1/%2").arg(path, imageFileName), errorStr))
			{
				ccLog::Warning(QString("[IcmFilter] Failed to load image %1 (%2)! Process stopped...").arg(imageFileName, errorStr));
				delete CI;
				fclose(fp);
				return loadedImages;
			}

			ccLog::Print("[IcmFilter] Image '%s' loaded", imageFileName);
			CI->setEnabled(false);
			CI->setName(imageFileName);
#ifdef INCLUDE_PHOTOS
			CI->setCompleteFileName(totalFileName);
#endif

			//FOV
			if (!fgets(line, MAX_ASCII_FILE_LINE_LENGTH, fp))
			{
				ccLog::Print("[IcmFilter] Read error (fieldOfView)!");
				delete CI;
				fclose(fp);
				return loadedImages;
			}

			float fov_rad = 0;
			sscanf(line, "\t fieldOfView %f\n", &fov_rad);

			float fov_deg = CCCoreLib::RadiansToDegrees(fov_rad);
			ccLog::Print("\t FOV=%f (degrees)", fov_deg);

			//Position
			float t[3];
			if (!fgets(line, MAX_ASCII_FILE_LINE_LENGTH, fp))
			{
				ccLog::Error("[IcmFilter] Read error (position)!");
				delete CI;
				fclose(fp);
				return loadedImages;
			}
			sscanf(line, "\t position %f %f %f\n", t, t + 1, t + 2);

			ccLog::Print("\t Camera pos=(%f,%f,%f)", t[0], t[1], t[2]);

			//Description
			char desc[MAX_ASCII_FILE_LINE_LENGTH];
			if (!fgets(line, MAX_ASCII_FILE_LINE_LENGTH, fp))
			{
				ccLog::Error("[IcmFilter] Read error (description)!");
				delete CI;
				fclose(fp);
				return loadedImages;
			}
			sscanf(line, "\t description \"%s\"\n", desc);

			//CI->setDescription(desc);
			ccLog::Print("\t Description: '%s'", desc);

			//Orientation
			float axis[3]{ 0.0f, 0.0f, 0.0f };
			float angle_rad = 0.0f;
			if (!fgets(line, MAX_ASCII_FILE_LINE_LENGTH, fp))
			{
				ccLog::Error("[IcmFilter] Read error (orientation)!");
				fclose(fp);
				return loadedImages;
			}
			sscanf(line, "\t orientation %f %f %f %f\n", axis, axis + 1, axis + 2, &angle_rad);

			ccLog::Print("\t Camera orientation=(%f,%f,%f)+[%f]", axis[0], axis[1], axis[2], angle_rad);

			ccCameraSensor::IntrinsicParameters params;
			params.vFOV_rad = fov_rad;
			params.arrayWidth = CI->getW();
			params.arrayHeight = CI->getH();
			params.principal_point[0] = params.arrayWidth / 2.0f;
			params.principal_point[1] = params.arrayHeight / 2.0f;
			params.vertFocal_pix = params.arrayHeight / (2 * tan(params.vFOV_rad / 2));
			params.pixelSize_mm[0] = params.pixelSize_mm[1] = 1.0f;
			ccCameraSensor* sensor = new ccCameraSensor(params);

			ccGLMatrix mat;
			mat.initFromParameters(angle_rad, CCVector3::fromArray(axis), CCVector3::fromArray(t));
			sensor->setRigidTransformation(mat);

			sensor->setGraphicScale(globalBBox.getDiagNorm() / 20);
			sensor->setVisible(true);
			sensor->setEnabled(false);
			CI->addChild(sensor);
			CI->setAssociatedSensor(sensor);

			entities->addChild(CI);
			++loadedImages;
		}
	}

	fclose(fp);

	return loadedImages;
}
//*/
