/// \file   mainwindow.hpp
/// \brief  Main window and algorhytms.
/// \author smal.root@gmail.com
/// \date   2016

#include "mainwindow.hpp"
#include "ui_mainwindow.h"

// Header files, Assimp.
#include <assimp/Exporter.hpp>
#include <assimp/postprocess.h>

#ifndef __unused
	#define __unused	__attribute__((unused))
#endif // __unused

/**********************************/
/************ Functions ***********/
/**********************************/

/********************************************************************/
/********************* Import/Export functions **********************/
/********************************************************************/

void MainWindow::ImportFile(const QString &pFileName)
{
using namespace Assimp;

QTime time_begin = QTime::currentTime();

	if(mScene != nullptr)
	{
		mImporter.FreeScene();
		mGLView->FreeScene();
	}

	// Try to import scene.
	mScene = mImporter.ReadFile(pFileName.toStdString(), aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_ValidateDataStructure | \
															aiProcess_GenUVCoords | aiProcess_TransformUVCoords | aiProcess_FlipUVs);
	if(mScene != nullptr)
	{
		ui->lblLoadTime->setText(QString::number(time_begin.secsTo(QTime::currentTime())));
		LogInfo("Import done: " + pFileName);
		// Prepare widgets for new scene.
		ui->leFileName->setText(pFileName.right(pFileName.length() - pFileName.lastIndexOf('/') - 1));
		ui->lstLight->clear();
		ui->lstCamera->clear();
		ui->cbxLighting->setChecked(true);	mGLView->Lighting_Enable();
		ui->cbxBBox->setChecked(false);		mGLView->Enable_SceneBBox(false);
		ui->cbxTextures->setChecked(true);	mGLView->Enable_Textures(true);
		//
		// Fill info labels
		//
		// Cameras
		ui->lblCameraCount->setText(QString::number(mScene->mNumCameras));
		// Lights
		ui->lblLightCount->setText(QString::number(mScene->mNumLights));
		// Meshes, faces, vertices.
		size_t qty_face = 0;
		size_t qty_vert = 0;

		for(size_t idx_mesh = 0; idx_mesh < mScene->mNumMeshes; idx_mesh++)
		{
			qty_face += mScene->mMeshes[idx_mesh]->mNumFaces;
			qty_vert += mScene->mMeshes[idx_mesh]->mNumVertices;
		}

		ui->lblMeshCount->setText(QString::number(mScene->mNumMeshes));
		ui->lblFaceCount->setText(QString::number(qty_face));
		ui->lblVertexCount->setText(QString::number(qty_vert));
		// Animation
		if(mScene->mNumAnimations)
			ui->lblHasAnimation->setText("yes");
		else
			ui->lblHasAnimation->setText("no");

		//
		// Set scene for GL viewer.
		//
		mGLView->SetScene(mScene, pFileName);
		// Select first camera
		ui->lstCamera->setCurrentRow(0);
		mGLView->Camera_Set(0);
		// Scene is loaded, do first rendering.
		LogInfo("Scene is ready for rendering.");
		mGLView->updateGL();
	}
	else
	{
		ResetSceneInfos();

		QString errorMessage = QString("Error parsing \'%1\' : \'%2\'").arg(pFileName).arg(mImporter.GetErrorString());
		QMessageBox::critical(this, "Import error", errorMessage);
		LogError(errorMessage);
	}// if(mScene != nullptr)
}

void MainWindow::ResetSceneInfos()
{
	ui->lblLoadTime->clear();
	ui->leFileName->clear();
	ui->lblMeshCount->setText("0");
	ui->lblFaceCount->setText("0");
	ui->lblVertexCount->setText("0");
	ui->lblCameraCount->setText("0");
	ui->lblLightCount->setText("0");
	ui->lblHasAnimation->setText("no");
}

/********************************************************************/
/************************ Logging functions *************************/
/********************************************************************/

void MainWindow::LogInfo(const QString& pMessage)
{
	Assimp::DefaultLogger::get()->info(pMessage.toStdString());
}

void MainWindow::LogError(const QString& pMessage)
{
	Assimp::DefaultLogger::get()->error(pMessage.toStdString());
}

/********************************************************************/
/*********************** Override functions ************************/
/********************************************************************/

void MainWindow::mousePressEvent(QMouseEvent* pEvent)
{
	if(pEvent->button() & Qt::LeftButton)
		mPosition_Pressed_LMB = pEvent->pos();
	else if(pEvent->button() & Qt::RightButton)
		mPosition_Pressed_RMB = pEvent->pos();
}

void MainWindow::mouseMoveEvent(QMouseEvent* pEvent)
{
	if(pEvent->buttons() & Qt::LeftButton)
	{
		GLfloat dx = 180 * GLfloat(pEvent->x() - mPosition_Pressed_LMB.x()) / mGLView->width();
		GLfloat dy = 180 * GLfloat(pEvent->y() - mPosition_Pressed_LMB.y()) / mGLView->height();

		if(pEvent->modifiers() & Qt::ShiftModifier)
			mGLView->Camera_RotateScene(dy, 0, dx);// Rotate around oX and oZ axises.
		else
			mGLView->Camera_RotateScene(dy, dx, 0);// Rotate around oX and oY axises.

		mGLView->updateGL();
		mPosition_Pressed_LMB = pEvent->pos();
	}

	if(pEvent->buttons() & Qt::RightButton)
	{
		GLfloat dx = 180 * GLfloat(pEvent->x() - mPosition_Pressed_RMB.x()) / mGLView->width();
		GLfloat dy = 180 * GLfloat(pEvent->y() - mPosition_Pressed_RMB.y()) / mGLView->height();

		if(pEvent->modifiers() & Qt::ShiftModifier)
			mGLView->Camera_Rotate(dy, 0, dx);// Rotate around oX and oZ axises.
		else
			mGLView->Camera_Rotate(dy, dx, 0);// Rotate around oX and oY axises.

		mGLView->updateGL();
		mPosition_Pressed_RMB = pEvent->pos();
	}
}

void MainWindow::keyPressEvent(QKeyEvent* pEvent)
{
GLfloat step;

	if(pEvent->modifiers() & Qt::ControlModifier)
		step = 10;
	else if(pEvent->modifiers() & Qt::AltModifier)
		step = 100;
	else
		step = 1;

	if(pEvent->key() == Qt::Key_A)
		mGLView->Camera_Translate(-step, 0, 0);
	else if(pEvent->key() == Qt::Key_D)
		mGLView->Camera_Translate(step, 0, 0);
	else if(pEvent->key() == Qt::Key_W)
		mGLView->Camera_Translate(0, step, 0);
	else if(pEvent->key() == Qt::Key_S)
		mGLView->Camera_Translate(0, -step, 0);
	else if(pEvent->key() == Qt::Key_Up)
		mGLView->Camera_Translate(0, 0, -step);
	else if(pEvent->key() == Qt::Key_Down)
		mGLView->Camera_Translate(0, 0, step);

	mGLView->updateGL();
}

/********************************************************************/
/********************** Constructor/Destructor **********************/
/********************************************************************/

MainWindow::MainWindow(QWidget *parent)
	: QMainWindow(parent), ui(new Ui::MainWindow),
		mScene(nullptr)
{
using namespace Assimp;

	ui->setupUi(this);
	// Create OpenGL widget
	mGLView = new CGLView(this);
	mGLView->setMinimumSize(800, 600);
	mGLView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
	mGLView->setFocusPolicy(Qt::StrongFocus);
	// Connect to GLView signals.
	connect(mGLView, SIGNAL(Paint_Finished(size_t, GLfloat)), SLOT(Paint_Finished(size_t, GLfloat)));
	connect(mGLView, SIGNAL(SceneObject_Camera(QString)), SLOT(SceneObject_Camera(QString)));
	connect(mGLView, SIGNAL(SceneObject_LightSource(QString)), SLOT(SceneObject_LightSource(QString)));
	// and add it to layout
	ui->hlMainView->insertWidget(0, mGLView, 4);
	// Create logger
	mLoggerView = new CLoggerView(ui->tbLog);
	DefaultLogger::create("", Logger::VERBOSE);
	DefaultLogger::get()->attachStream(mLoggerView, DefaultLogger::Debugging | DefaultLogger::Info | DefaultLogger::Err | DefaultLogger::Warn);

	ResetSceneInfos();
}

MainWindow::~MainWindow()
{
using namespace Assimp;

	DefaultLogger::get()->detatchStream(mLoggerView, DefaultLogger::Debugging | DefaultLogger::Info | DefaultLogger::Err | DefaultLogger::Warn);
	DefaultLogger::kill();

	if(mScene != nullptr) mImporter.FreeScene();
	if(mLoggerView != nullptr) delete mLoggerView;
	if(mGLView != nullptr) delete mGLView;
	delete ui;
}

/********************************************************************/
/****************************** Slots *******************************/
/********************************************************************/

void MainWindow::Paint_Finished(const size_t pPaintTime_ms, const GLfloat pDistance)
{
	ui->lblRenderTime->setText(QString::number(pPaintTime_ms));
	ui->lblDistance->setText(QString::number(pDistance));
}

void MainWindow::SceneObject_Camera(const QString& pName)
{
	ui->lstCamera->addItem(pName);
}

void MainWindow::SceneObject_LightSource(const QString& pName)
{
	ui->lstLight->addItem(pName);
	// After item added "currentRow" is still contain old value (even '-1' if first item added). Because "currentRow"/"currentItem" is changed by user interaction,
	// not by "addItem". So, "currentRow" must be set manually.
	ui->lstLight->setCurrentRow(ui->lstLight->count() - 1);
	// And after "selectAll" handler of "signal itemSelectionChanged" will get right "currentItem" and "currentRow" values.
	ui->lstLight->selectAll();
}

void MainWindow::on_butOpenFile_clicked()
{
aiString filter_temp;
QString filename, filter;

	mImporter.GetExtensionList(filter_temp);
	filter = filter_temp.C_Str();
	filter.replace(';', ' ');
	filter.append(" ;; All (*.*)");
	filename = QFileDialog::getOpenFileName(this, "Choose the file", "", filter);

	if(!filename.isEmpty()) ImportFile(filename);
}


void MainWindow::on_butExport_clicked()
{
using namespace Assimp;

QString filename, filter, format_id;
Exporter exporter;
QTime time_begin;
aiReturn rv;
QStringList exportersList;
QMap<QString, const aiExportFormatDesc*> exportersMap;


	if(mScene == nullptr)
	{
		QMessageBox::critical(this, "Export error", "Scene is empty");

		return;
	}

	for (int i = 0; i < exporter.GetExportFormatCount(); ++i)
	{
		const aiExportFormatDesc* desc = exporter.GetExportFormatDescription(i);
		exportersList.push_back(desc->id + QString(": ") + desc->description);
		exportersMap.insert(desc->id, desc);
	}

	// get an exporter
	bool dialogSelectExporterOk;
	QString selectedExporter = QInputDialog::getItem(this, "Export format", "Select the exporter : ", exportersList, 0, false, &dialogSelectExporterOk);
	if (!dialogSelectExporterOk)
		return;

	// build the filter
	QString selectedId = selectedExporter.left(selectedExporter.indexOf(':'));
	filter = QString("*.") + exportersMap[selectedId]->fileExtension;

	// get file path
	filename = QFileDialog::getSaveFileName(this, "Set file name", "", filter);
	// if it's canceled
	if (filename == "")
		return;

	// begin export
	time_begin = QTime::currentTime();
	rv = exporter.Export(mScene, selectedId.toLocal8Bit(), filename.toLocal8Bit(), aiProcess_FlipUVs);
	ui->lblExportTime->setText(QString::number(time_begin.secsTo(QTime::currentTime())));
	if(rv == aiReturn_SUCCESS)
		LogInfo("Export done: " + filename);
	else
	{
		QString errorMessage = QString("Export failed: ") + filename;
		LogError(errorMessage);
		QMessageBox::critical(this, "Export error", errorMessage);
	}
}

void MainWindow::on_cbxLighting_clicked(bool pChecked)
{
	if(pChecked)
		mGLView->Lighting_Enable();
	else
		mGLView->Lighting_Disable();

	mGLView->updateGL();
}

void MainWindow::on_lstLight_itemSelectionChanged()
{
bool selected = ui->lstLight->isItemSelected(ui->lstLight->currentItem());

	if(selected)
		mGLView->Lighting_EnableSource(ui->lstLight->currentRow());
	else
		mGLView->Lighting_DisableSource(ui->lstLight->currentRow());

	mGLView->updateGL();
}

void MainWindow::on_lstCamera_clicked( const QModelIndex &)
{
	mGLView->Camera_Set(ui->lstLight->currentRow());
	mGLView->updateGL();
}

void MainWindow::on_cbxBBox_clicked(bool checked)
{
	mGLView->Enable_SceneBBox(checked);
	mGLView->updateGL();
}

void MainWindow::on_cbxTextures_clicked(bool checked)
{
	mGLView->Enable_Textures(checked);
	mGLView->updateGL();
}