#include <QMessageBox>
#include <QFileDialog>
#include <QLabel>
#include <QStatusBar>
#include <QSettings>
#include <QCloseEvent>
#include <QPlainTextEdit>
#include <QAbstractItemModel>
#include <QStandardItemModel>
#include <QGroupBox>
#include <QColorDialog>
#include <QComboBox>
#include <math.h> 
#include "main_window.h"
#include "paint_canvas.h"
#include "../geometry/file_io.h"
#include "../geometry/point_set.h"
#include "../widget/logger.h"
#include "../widget/message_box.h"
#include "../geometry/vertex_group.h"
#include "../basic/file_system.h"
#include "../widget/logger.h"
#include "dlg_down_sampling.h"
#include "dlg_rotate_point_cloud.h"
#include "tree_widge_titem.h"


using namespace std;
using namespace qglviewer;

MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags)
	: QMainWindow(parent, flags)
	, curDataDirectory_(".")
	, dlgDownSampling_(nil)
	, dlgRotatePointCloud_(nil)
{	
	setupUi(this);

	//////////////////////////////////////////////////////////////////////////

	QGLFormat format = QGLFormat::defaultFormat();
	format.setSampleBuffers(true);
	format.setSamples(8);

	mainCanvas_ = new PaintCanvas(format, this);
	mainCanvas_->setAttribute(Qt::WA_MouseTracking);

	setCentralWidget(mainCanvas_);
	Logger::init(mainCanvas_);

	dockWidgetOutput->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);

	//////////////////////////////////////////////////////////////////////////

	QStringList headerLabels;
	headerLabels << "Index" << "Label" << "Conf" << "#Point";
	treeWidget->setHeaderLabels(headerLabels);
	//treeWidget->setSortingEnabled(true);

	// for the index column, narrow the width
	treeWidget->setColumnWidth(0, 80);

	connect(treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(treeItemDataChanged(QTreeWidgetItem*, int)));

	//////////////////////////////////////////////////////////////////////////

	createStatusBar();
	createActions();
	createMenus();

	readSettings();
	setWindowTitle("PcdViewer");

	setContextMenuPolicy(Qt::CustomContextMenu);
	setWindowIcon(QIcon(":/Resources/icon.png"));

	//setFixedSize(1200, 900);
	setWindowState(Qt::WindowMaximized);

	setFocusPolicy(Qt::ClickFocus);

	mainCanvas_->camera()->setType(Camera::PERSPECTIVE);
	showMaximized();
}

MainWindow::~MainWindow()
{
	if (dlgDownSampling_)
		delete dlgDownSampling_;

	if (dlgRotatePointCloud_)
		delete dlgRotatePointCloud_;
}

void MainWindow::createMenus() {
	actionSeparator = menuFile->addSeparator();

	QList<QAction*> actions;
	for (int i = 0; i < MaxRecentFiles; ++i)
		actions.push_back(actionsRecentFile[i]);
	menuFile->insertActions(actionExit, actions);
}

void MainWindow::createActionsForFileMenu() {
	//////////////////////////////////////////////////////////////////////////
	for (int i = 0; i < MaxRecentFiles; ++i) {
		actionsRecentFile[i] = new QAction(this);
		actionsRecentFile[i]->setVisible(false);
		connect(actionsRecentFile[i], SIGNAL(triggered()), this, SLOT(openRecentFile()));
	}

	connect(actionImportPointCloud, SIGNAL(triggered()), this, SLOT(importPointCloud()));
	connect(actionExportPointCloud, SIGNAL(triggered()), this, SLOT(exportPointCloud()));
	actionImportPointCloud->setShortcut(QString("Ctrl+O"));
	actionExportPointCloud->setShortcut(QString("Ctrl+S"));

	connect(actionSnapshot, SIGNAL(triggered()), this, SLOT(snapshot()));
	connect(actionRecordCameraState, SIGNAL(triggered()), this, SLOT(recordCameraState()));
	connect(actionRestoreCameraState, SIGNAL(triggered()), this, SLOT(restoreCameraState()));
	
	connect(actionExit, SIGNAL(triggered()), this, SLOT(close()));
	actionExit->setShortcut(QString("Ctrl+Q"));
}

void MainWindow::createActionsForViewMenu() {
	connect(actionPerspectiveOrthographic, SIGNAL(toggled(bool)), mainCanvas_, SLOT(setPerspectiveMode(bool)));
	mainCanvas_->setPerspectiveMode(false);
	
	connect(actionFullScreen, SIGNAL(toggled(bool)), this, SLOT(setFullScreen(bool)));
	connect(actionFitWindow, SIGNAL(triggered()), mainCanvas_, SLOT(fitScreen()));

	connect(actionIncreasePointSize, SIGNAL(triggered()), mainCanvas_, SLOT(increasePointSize()));
	connect(actionDecreasePointSize, SIGNAL(triggered()), mainCanvas_, SLOT(decreasePointSize()));

	connect(actionShowCoordinateSystem, SIGNAL(toggled(bool)), mainCanvas_, SLOT(setShowCoordinateSystem(bool)));

	connect(actionAssignColorByHeight, SIGNAL(triggered()), mainCanvas_, SLOT(assignColorByHeight()));
	connect(actionAssignColorByNormal, SIGNAL(triggered()), mainCanvas_, SLOT(assignColorByNormal()));
}


void MainWindow::createActionsForSelectMenu() {
	QActionGroup* toolActionGroup = new QActionGroup(this);
	toolActionGroup->addAction(actionSceneManipulation);
	toolActionGroup->addAction(actionSelectPointsByRectangle);
	toolActionGroup->addAction(actionSelectPointsByLasso);

	connect(toolActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(operationModeChanged(QAction*)));
}


void MainWindow::createActionsForEditMenu() {
	connect(actionShowNormals, SIGNAL(toggled(bool)), mainCanvas_, SLOT(setShowNormals(bool)));

	connect(actionDeleteSelectedPoints, SIGNAL(triggered()), mainCanvas_, SLOT(deleteSelectedPoints()));
	connect(actionDownSampling, SIGNAL(triggered()), this, SLOT(downSampling()));

	connect(actionRotatePointCloud, SIGNAL(triggered()), this, SLOT(rotatePointCloud()));
}


void MainWindow::createActions() {
	// file menu
	createActionsForFileMenu();

	// view menu
	createActionsForViewMenu();

	// select
	createActionsForSelectMenu();

	// edit menu 
	createActionsForEditMenu();

	// about menu
	connect(actionAbout, SIGNAL(triggered()), this, SLOT(about()));
	connect(actionAboutQt, SIGNAL(triggered()), this, SLOT(aboutQt()));
}


void MainWindow::createStatusBar()
{	
	statusLabel = new QLabel("Ready");
	statusLabel->setAlignment(Qt::AlignHCenter);
	statusLabel->setMinimumSize(statusLabel->sizeHint());

	spacerLabel = new QLabel;

	coordinateUnderMouseLabel = new QLabel(this);
	coordinateUnderMouseLabel->setAlignment(Qt::AlignLeft);
	//coordinateUnderMouseLabel->setMinimumSize(coordinateUnderMouseLabel->sizeHint());

	numVerticesLabel = new QLabel;
	numVerticesLabel->setIndent(80);
	numVerticesLabel->setAlignment(Qt::AlignRight);
	//numVerticesLabel->setMinimumSize(numVerticesLabel->sizeHint());

	numVertexGroupsLabel = new QLabel;
	numVertexGroupsLabel->setIndent(80);
	numVertexGroupsLabel->setAlignment(Qt::AlignRight);
	//numVertexGroupsLabel->setMinimumSize(numVerticesLabel->sizeHint());

	statusBar()->addWidget(statusLabel);
	statusBar()->addWidget(spacerLabel, 0);
	statusBar()->addWidget(coordinateUnderMouseLabel, 1);
	statusBar()->addWidget(numVerticesLabel, 0);
	statusBar()->addWidget(numVertexGroupsLabel, 0);

	updateStatusBar();
	updateAnnotationTable();
}

void MainWindow::updateStatusBar()
{
	unsigned int nb_points = 0;
	unsigned int nb_groups = 0;
	PointSet* ps = mainCanvas_->pointSet();
	if (ps != nil) {
		nb_points = ps->nb_vertices();
		nb_groups = ps->vertex_groups().size();
	}

	numVerticesLabel->setText( QString("#points: %1").arg(nb_points) );
	numVertexGroupsLabel->setText( QString("#groups: %1").arg(nb_groups) );
}

void MainWindow::treeItemDataChanged(QTreeWidgetItem* it, int col) {
	if (col == 1) {// change label
		TreeWidgetItem* item = dynamic_cast<TreeWidgetItem*>(it);
		std::string new_label = item->data(col, Qt::DisplayRole).toString().toStdString();
		
		// replace all spaces with '-'
		for (unsigned int i=0; i<new_label.size(); ++i) {
			char c = new_label[i];
			if (c == ' ')
				new_label[i] = '-';
		}

		item->setData(col, Qt::DisplayRole, QString::fromStdString(new_label));
		
		VertexGroup* g = item->vertex_group();
		g->set_label(new_label);
	} 
	else if (col == 2) {// change confidence
		TreeWidgetItem* item = dynamic_cast<TreeWidgetItem*>(it);
		float new_conf = item->data(col, Qt::DisplayRole).toFloat();
		item->setData(col, Qt::DisplayRole, new_conf);

		VertexGroup* g = item->vertex_group();
		g->set_confidence(new_conf);
	}
}


void MainWindow::updateAnnotationTable() {
	PointSet* ps = mainCanvas_->pointSet();
	if (ps == nil)
		return;
	
	// clear current :-) tricky
	treeWidget->setCurrentItem(nil);

	//////////////////////////////////////////////////////////////////////////

	std::vector<VertexGroup*>& groups = ps->vertex_groups();
	unsigned int item_count = treeWidget->topLevelItemCount();

	// step 1. remove redundant top level tree items
	if (item_count > groups.size()) { // remove items that will not be used
		unsigned int num_to_remove = item_count - groups.size();
		for (unsigned int i=0; i<num_to_remove; ++i) {
			// remove last top level item
			int idx_top_item = treeWidget->topLevelItemCount() - 1;
			QTreeWidgetItem* top_item = treeWidget->topLevelItem(idx_top_item);
			// delete item; // no need, Qt will do this

			treeWidget->takeTopLevelItem(idx_top_item);
			//treeWidget->removeItemWidget(top_item, 0);
		}
	}

	// step 1. remove redundant child level tree items
	item_count = treeWidget->topLevelItemCount();
	for (unsigned int i=0; i<item_count; ++i) {
		if (groups.size() > i) { // make sure the group corresponding to current item does exist
			QTreeWidgetItem* top_item = treeWidget->topLevelItem(i);
			
			unsigned int item_chld_count = top_item->childCount();
			unsigned int group_chld_count = groups[i]->children().size();
			if (item_chld_count > group_chld_count) {
				unsigned int num_chld_to_remove = item_chld_count - group_chld_count;
				for (unsigned int j=0; j<num_chld_to_remove; ++j) { 
					int idx_chld_item = top_item->childCount() - 1;
					//top_item->removeChild(top_item->child(idx_chld_item));	// remove last child item
					top_item->takeChild(idx_chld_item);	// remove last child item
				}
			}
		}
	}

	if (groups.empty())
		return;

	//////////////////////////////////////////////////////////////////////////

	for (unsigned int i=0; i<groups.size(); ++i) {
		TreeWidgetItem* item = dynamic_cast<TreeWidgetItem*>(treeWidget->topLevelItem(i));
		if (item == nil) {
			item = new TreeWidgetItem;
			item->setFlags(item->flags() | Qt::ItemIsEditable);
			treeWidget->addTopLevelItem(item);
		}		

		VertexGroup* g = groups[i];
		Colorf		 c = g->color();
		QColor		 color(c.r() * 255, c.g() * 255, c.b() * 255);
		QString      label = QString::fromStdString(g->label());
		float		 conf = g->confidence();
		unsigned int num_point = g->size();

		item->set_vertex_group(g);
		item->setData(0, Qt::DisplayRole, i+1);
		item->setData(1, Qt::DecorationRole, color);
		item->setData(1, Qt::DisplayRole, label);
		item->setData(2, Qt::DisplayRole, conf);
		item->setData(3, Qt::DisplayRole, num_point);

		// the children
		std::vector<VertexGroup*> children = g->children();
		for (unsigned int j=0; j<children.size(); ++j) {
			VertexGroup* chd = children[j];
			
			c = chd->color();
			color = QColor(c.r() * 255, c.g() * 255, c.b() * 255);			
			label = QString::fromStdString(chd->label());
			conf = chd->confidence();
			num_point = chd->size();
		
			TreeWidgetItem* chd_item = dynamic_cast<TreeWidgetItem*>(item->child(j));
			if (chd_item == nil) {
				chd_item = new TreeWidgetItem;
				chd_item->setFlags(chd_item->flags() | Qt::ItemIsEditable);
				item->addChild(chd_item);
			}	

			chd_item->set_vertex_group(chd);
			chd_item->setData(0, Qt::DisplayRole, QString("(%1)").arg(j+1));
			chd_item->setData(1, Qt::DecorationRole, color);
			chd_item->setData(1, Qt::DisplayRole, label);
			chd_item->setData(2, Qt::DisplayRole, conf);
			chd_item->setData(3, Qt::DisplayRole, num_point);
		}
	}
	
	treeWidget->update();
}

void MainWindow::showCoordinateUnderMouse(const Point3f& p, bool found) {
	if (found) {
		QString coordString = QString(" XYZ = [%1, %2, %3]").arg(p.x()).arg(p.y()).arg(p.z());
		coordinateUnderMouseLabel->setText(coordString);
	} else {
		coordinateUnderMouseLabel->setText(" XYZ = [-0.0, -0.0, -0.0]");
	}
}

void MainWindow::showPixelPositionUnderMouse(const QPoint& p) {
	QString coordString = QString(" XY = [%1, %2]").arg(p.x()).arg(p.y());
	coordinateUnderMouseLabel->setText(coordString);
}


void MainWindow::about()
{
	QMessageBox::about(this, tr("About PcdViewer"), 
		tr("This is my point cloud processing framework.\n"
		"Contact: liangliang.nan@gmail.com"));
}


void MainWindow::aboutQt()
{
	QMessageBox::aboutQt(this, tr("About Qt"));
}


void MainWindow::showStatusMessage(const QString &text, int timeout/* = 0*/) {
	statusBar()->showMessage(text, timeout);
	updateStatusBar();
}

void MainWindow::setFullScreen(bool b)
{
	if (b)
		showFullScreen();
	else
		showMaximized();
}


void MainWindow::readSettings()
{
	QSettings settings("SIAT_VCC", "PcdViewer");

	recentFiles_ = settings.value("recentFiles").toStringList();
	updateRecentFileActions();

	curDataDirectory_ = settings.value("currentDirectory").toString();	
}

void MainWindow::writeSettings()
{
	QSettings settings("SIAT_VCC", "PcdViewer");
	settings.setValue("recentFiles", recentFiles_);
	settings.setValue("currentDirectory", curDataDirectory_);
}

void MainWindow::closeEvent(QCloseEvent *event)
{
	if (okToContinue()) {
		writeSettings();
		event->accept();
	} else {
		event->ignore();
	}
}

bool MainWindow::okToContinue()
{
	if (isWindowModified()) {
		int r = QMessageBox::warning(this, tr("PcdViewer"),
			tr("The model has been modified.\n"
			"Do you want to save your changes?"),
			QMessageBox::Yes | QMessageBox::Default,
			QMessageBox::No,
			QMessageBox::Cancel | QMessageBox::Escape);
		if (r == QMessageBox::Yes)
			return exportPointCloud();
		else if (r == QMessageBox::Cancel)
			return false;
	}
	return true;
}

bool MainWindow::loadPointCloud(const QString &fileName)
{
	if (fileName == curPointCloudFileName_)
		return false;

	PointSet* ptc = FileIO::read_point_cloud(fileName.toStdString());

	if (ptc == nil) {	
		showStatusMessage(tr("Loading failed"), 500);
		return false;
	} else {	
		mainCanvas_->setPointSet(ptc);

		setCurrentPointCloudFile(fileName);
		showStatusMessage(tr("File loaded"), 500);

		mainCanvas_->updateCanvas();
		return true;
	}
}


bool MainWindow::savePointCloud(const QString &fileName)
{
	PointSet* pointSet = mainCanvas_->pointSet();
	if (pointSet == nil)
		return false;

	bool result = FileIO::save_point_cloud(pointSet, fileName.toStdString());
	if (result) {
		setCurrentPointCloudFile(fileName);
		showStatusMessage(tr("File saved"), 500);
		return true;
	} else {
		showStatusMessage(tr("Saving failed"), 500);
		return false;
	}
}

void MainWindow::setCurrentPointCloudFile(const QString &fileName)
{
	curPointCloudFileName_ = fileName;
	curDataDirectory_ = fileName.left(fileName.lastIndexOf("/"));

	setWindowModified(false);

	QString shownName = "Untitled";
	if (!curPointCloudFileName_.isEmpty()) {
		shownName = strippedName(curPointCloudFileName_);
		recentFiles_.removeAll(curPointCloudFileName_);
		recentFiles_.prepend(curPointCloudFileName_);
		updateRecentFileActions();
	}

	setWindowTitle(tr("%1[*] - %2").arg(shownName).arg(tr("PcdViewer")));
}


void MainWindow::openRecentFile()
{
	if (okToContinue()) {
		QAction *action = qobject_cast<QAction *>(sender());
		if (action) {
			QString filename(action->data().toString());
			QFileInfo info(action->data().toString());
			QString ext = info.suffix().toLower();
		    if (ext == "blab" ||
				ext == "bxyz" || 
				ext == "bpn" ||
				ext == "lab"  ||
				ext == "xyz"  || 
				ext == "pn"  ||
				ext == "pcd") 
			{
				loadPointCloud(action->data().toString());
			}
		}
	}
}


bool MainWindow::importPointCloud()
{
	if (okToContinue()) {
		QString fileName = QFileDialog::getOpenFileName(this,
			tr("Read point cloud file"), curDataDirectory_,
			tr(
			"Point cloud (*.blab *.bxyz *.bpn *.lab *.xyz *.pn *.pcd)\n"
			"All files (*.*)")
			);

		if (fileName.isEmpty())
			return false;

		return loadPointCloud(fileName);
	}
	else
		return false;
}

bool MainWindow::exportPointCloud() {
	QString fileName = QFileDialog::getSaveFileName(this,
		tr("Save point cloud"), curPointCloudFileName_,
		tr(
		"Point cloud (*.blab *.bxyz *.bpn *.lab *.xyz *.pn *.pcd)\n"
		"All files (*.*)")
		);

	if (fileName.isEmpty())
		return false;

	return savePointCloud(fileName) ;
}


//"JPEG, PNG, EPS, PS, PPM, BMP"
bool MainWindow::snapshot() {
	mainCanvas_->saveSnapshot(false);

	return true;
}


void MainWindow::recordCameraState() {
	QString fileName = QFileDialog::getSaveFileName(this,
		tr("Record Camera State"), curPointCloudFileName_ + "_camera.cs",
		tr("Camera state file (*.cs)"));

	if (fileName.isEmpty())
		return ;

	if (mainCanvas_) {
		mainCanvas_->setStateFileName(fileName);
		mainCanvas_->saveStateToFile();
		mainCanvas_->setStateFileName("");
	} 
}


void MainWindow::restoreCameraState() {
	QString fileName = QFileDialog::getOpenFileName(this,
		tr("Restore Camera State"), curDataDirectory_,
		tr("Camera state file (*.cs)"));

	if (fileName.isEmpty())
		return ;

	if (mainCanvas_) {
		mainCanvas_->setStateFileName(fileName);
		mainCanvas_->restoreStateFromFile();
		mainCanvas_->setStateFileName("");

		mainCanvas_->updateCanvas();
	} 
}

void MainWindow::updateRecentFileActions()
{
	QMutableStringListIterator i(recentFiles_);
	while (i.hasNext()) {
		if (!QFile::exists(i.next()))
			i.remove();
	}

	for (int j = 0; j < MaxRecentFiles; ++j) {
		if (j < recentFiles_.count()) {
			QString text = tr("&%1 %2").arg(j + 1).arg(strippedName(recentFiles_[j]));
			actionsRecentFile[j]->setText(text);
			actionsRecentFile[j]->setData(recentFiles_[j]);
			actionsRecentFile[j]->setVisible(true);
		} else {
			actionsRecentFile[j]->setVisible(false);
		}
	}

	actionSeparator->setVisible(!recentFiles_.isEmpty());
}

QString MainWindow::strippedName(const QString &fullFileName)
{
	return QFileInfo(fullFileName).fileName();
}


SceneGraph* MainWindow::output(const std::string& msg) { 
	std::cout << msg ;

	plainTextEditOutput->moveCursor(QTextCursor::End);
	plainTextEditOutput->insertPlainText(QString::fromStdString(msg));
	plainTextEditOutput->repaint();
	plainTextEditOutput->update();
	return mainCanvas_;
} 


void MainWindow::downSampling() {
	if (dlgDownSampling_ == nil) 
		dlgDownSampling_ = new DlgDownSampling(this);

	dlgDownSampling_->show();
	dlgDownSampling_->raise();
	dlgDownSampling_->activateWindow();
}

void MainWindow::rotatePointCloud() {
	if (dlgRotatePointCloud_ == nil) 
		dlgRotatePointCloud_ = new DlgRotatePointCloud(this);

	dlgRotatePointCloud_->show();
	dlgRotatePointCloud_->raise();
	dlgRotatePointCloud_->activateWindow();
}


void MainWindow::operationModeChanged(QAction* act) {
	if (act == actionSceneManipulation) {
		mainCanvas_->sceneManipulation();
	} 

	else if (act == actionSelectPointsByRectangle) {
		mainCanvas_->selectPointsByRectangle();
	} 
	
	else if (act == actionSelectPointsByLasso) {
		mainCanvas_->selectPointsByLasso();
	}

	mainCanvas_->updateCanvas();
}