/*
    QGL SceneTree
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "scene_tree.h"

#include "glcamera.h"
#include "qglview.h"
#include "ui_scene_tree.h"

#include <QAction>
#include <QEvent>
#include <QScrollBar>
#include <QTreeWidget>

enum Column {
	cName,
	cVis,
	cMaterial
};
enum ItemRole {
	irObject = Qt::UserRole,
	irType   = Qt::UserRole + 1,
};

enum ObjectType {
	otNode   = 1,
	otMesh   = 2,
	otLight  = 4,
	otCamera = 8,
};


QAction * newSeparator() {
	QAction * s = new QAction();
	s->setSeparator(true);
	return s;
}


SceneTree::SceneTree(QWidget * parent): QWidget(parent) {
	ui = new Ui::SceneTree();
	ui->setupUi(this);
	ui->treeObjects->header()->setSectionResizeMode(cVis, QHeaderView::ResizeToContents);
	ui->treeObjects->header()->setSectionsMovable(false);
	ui->treeObjects->header()->swapSections(cName, cVis);
	ui->treeObjects->setItemDelegateForColumn(1, new NoEditDelegate(this));
	ui->treeObjects->setItemDelegateForColumn(2, new NoEditDelegate(this));
	icon_empty  = QIcon(":/icons/type-empty.png");
	icon_geo    = QIcon(":/icons/type-geo.png");
	icon_camera = QIcon(":/icons/type-camera.png");
	icon_light  = QIcon(":/icons/type-light.png");
	icon_vis[0] = QIcon(":/icons/layer-visible-off.png");
	icon_vis[1] = QIcon(":/icons/layer-visible-on.png");
	ui->treeObjects->addActions(actionsSelection());
	ui->treeObjects->addAction(newSeparator());
	ui->treeObjects->addActions(actionsAdd());
	ui->buttonFilter->addActions(QList<QAction *>()
	                             << ui->actionFilter_node << ui->actionFilter_mesh << ui->actionFilter_light << ui->actionFilter_camera);
	view             = 0;
	hidden_by_filter = obj_count = 0;
	block_tree                   = false;
	connect(ui->treeObjects->selectionModel(),
	        SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
	        this,
	        SLOT(treeObjects_selectionCnahged()));
}


SceneTree::~SceneTree() {
	delete ui;
}


void SceneTree::assignQGLView(QGLView * v) {
	view = v;
	objectsTreeChanged();
	if (!view) return;
	connect(view, SIGNAL(selectionChanged()), this, SLOT(selectionChanged()));
	connect(view, SIGNAL(materialsChanged()), this, SLOT(materialsChanged()));
	connect(view->scene(), SIGNAL(treeChanged()), this, SLOT(objectsTreeChanged()));
	connect(view->scene(), SIGNAL(__objectDeleted(ObjectBase *)), this, SLOT(__objectDeleted(ObjectBase *)));
	view->setContextActions(actionsSelection());
	checkActions();
}


QList<QAction *> SceneTree::actionsAdd() {
	QList<QAction *> ret;
	ret << ui->actionAdd_node << ui->actionAdd_light << ui->actionAdd_camera;
	return ret;
}


QList<QAction *> SceneTree::actionsSelection() {
	QList<QAction *> ret;
	ret << ui->actionFocus << newSeparator() << ui->actionGroup << ui->actionClone << newSeparator() << ui->actionSelect_parent
		<< ui->actionSelect_by_mesh << ui->actionSelect_by_material << newSeparator() << ui->actionMove_to_parent
		<< ui->actionTransfer_transform_to_children << newSeparator() << ui->actionActive_camera << ui->actionDefault_camera
		<< newSeparator() << ui->actionRemove;
	return ret;
}


void SceneTree::expandItems() {
	ui->treeObjects->expandAll();
}


void SceneTree::changeEvent(QEvent * e) {
	QWidget::changeEvent(e);
	if (e->type() == QEvent::LanguageChange) {
		ui->retranslateUi(this);
		return;
	}
}


void SceneTree::rememberExpanded(QTreeWidgetItem * ti) {
	for (int i = 0; i < ti->childCount(); ++i) {
		QTreeWidgetItem * ci = ti->child(i);
		if (ci->isExpanded()) expanded_ << itemObject(ci);
		rememberExpanded(ci);
	}
}


void SceneTree::restoreExpanded(QTreeWidgetItem * ti) {
	for (int i = 0; i < ti->childCount(); ++i) {
		QTreeWidgetItem * ci = ti->child(i);
		ci->setExpanded(expanded_.contains(itemObject(ci)));
		restoreExpanded(ci);
	}
}


void SceneTree::makeObjetTree(ObjectBase * o, QTreeWidgetItem * ti) {
	++obj_count;
	for (int i = 0; i < o->childCount(); ++i) {
		ObjectBase * co      = o->child(i);
		QTreeWidgetItem * ci = new QTreeWidgetItem(ti);
		ci->setText(cName, co->name());
		ci->setCheckState(cVis, co->isVisible() ? Qt::Checked : Qt::Unchecked);
		ci->setIcon(cVis, icon_vis[co->isVisible(true)]);
		if (co->material()) ci->setText(cMaterial, co->material()->name);
		ci->setFlags(ci->flags() | Qt::ItemIsEditable);
		ObjectType t = otNode;
		switch (co->type()) {
		case ObjectBase::glMesh:
			if (co->mesh()) {
				t = otMesh;
				ci->setIcon(cName, icon_geo);
				geo_items << ci;
			} else {
				ci->setIcon(cName, icon_empty);
			}
			break;
		case ObjectBase::glLight:
			t = otLight;
			ci->setIcon(cName, icon_light);
			break;
		case ObjectBase::glCamera:
			t = otCamera;
			ci->setIcon(cName, icon_camera);
			ci->setText(cVis, (co == view->camera()) ? "*" : "");
			cam_items << ci;
			break;
		default: break;
		}
		ci->setData(cName, irObject, quintptr(co));
		ci->setData(cName, irType, int(t));
		ci->setSelected(co->isSelected());
		makeObjetTree(co, ci);
	}
}


ObjectBase * SceneTree::itemObject(QTreeWidgetItem * item) const {
	if (!item) return 0;
	return (ObjectBase *)(item->data(cName, irObject).toULongLong());
}


ObjectBase * SceneTree::insertTarget() const {
	auto sil = ui->treeObjects->selectedItems();
	QSet<ObjectBase *> parents;
	for (auto * i: sil) {
		auto * p = itemObject(i);
		if (p) p = p->parent();
		if (p) parents << p;
	}
	if (parents.size() != 1) return nullptr;
	return parents.values()[0];
}


int SceneTree::itemType(QTreeWidgetItem * item) const {
	if (!item) return otNode;
	return item->data(cName, irType).toInt();
}


void SceneTree::selectionChanged() {
	if (block_tree) return;
	block_tree                  = true;
	QList<QTreeWidgetItem *> il = ui->treeObjects->findItems("", Qt::MatchContains | Qt::MatchRecursive);
	const ObjectBase * fo       = 0;
	if (view->selectedObjects().size() == 1) fo = view->selectedObject();
	foreach(QTreeWidgetItem * i, il) {
		ObjectBase * o = itemObject(i);
		i->setSelected(o->isSelected());
		if (fo && (fo == o)) {
			ui->treeObjects->setCurrentItem(i);
		}
	}
	block_tree = false;
	checkActions();
}


void SceneTree::materialsChanged() {
	foreach(QTreeWidgetItem * i, geo_items) {
		ObjectBase * o = itemObject(i);
		if (!o) continue;
		if (o->material()) i->setText(cMaterial, o->material()->name);
	}
}


void SceneTree::cameraChanged() {
	if (!view) return;
	foreach(QTreeWidgetItem * i, cam_items) {
		ObjectBase * o = itemObject(i);
		if (!o) continue;
		i->setText(cVis, (o == view->camera()) ? "*" : "");
	}
}


bool SceneTree::filterTree(QTreeWidgetItem * ti, const QString & filter, int types) {
	bool ret = false;
	for (int i = 0; i < ti->childCount(); ++i) {
		QTreeWidgetItem * ci = ti->child(i);
		QString cit          = ci->text(cName);
		int t                = itemType(ci);
		if (ci->childCount() > 0) {
			if (!filterTree(ci, filter, types)) {
				ci->setHidden(true);
				++hidden_by_filter;
				continue;
			}
			ci->setHidden(false);
			ret = true;
		} else {
			bool f = false;
			if (filter.isEmpty()) {
				f = true;
			} else {
				f = f || cit.contains(filter, Qt::CaseInsensitive);
			}
			if ((types & t) != t) f = false;
			ci->setHidden(!f);
			if (f)
				ret = true;
			else
				++hidden_by_filter;
		}
	}
	return ret;
}


void SceneTree::checkActions() {
	bool has_1 = false, has_m = false;
	bool has_cam    = false;
	bool has_mesh   = false;
	bool is_def_cam = false;
	if (view) {
		is_def_cam         = view->isDefaultCamera();
		ObjectBaseList slo = view->selectedObjects();
		has_1              = !slo.isEmpty();
		has_m              = slo.size() > 1;
		for (ObjectBase * o: slo) {
			if (o->type() == ObjectBase::glCamera) has_cam = (slo.size() == 1);
			if (o->type() == ObjectBase::glMesh) has_mesh = true;
		}
	}

	ui->actionFocus->setEnabled(has_mesh);
	ui->actionRemove->setEnabled(has_1);
	ui->actionClone->setEnabled(has_1);
	ui->actionGroup->setEnabled(has_m);
	ui->actionTransfer_transform_to_children->setEnabled(has_1);
	ui->actionMove_to_parent->setEnabled(has_1);
	ui->actionSelect_parent->setEnabled(has_1);
	ui->actionSelect_by_mesh->setEnabled(has_mesh);
	ui->actionSelect_by_material->setEnabled(has_mesh);
	ui->actionActive_camera->setEnabled(has_cam);
	ui->actionDefault_camera->setEnabled(!is_def_cam);
}


void SceneTree::treeObjects_selectionCnahged() {
	if (block_tree || !view) return;
	block_tree = true;
	view->scene()->clearSelection();
	ObjectBaseList sol;
	QList<QTreeWidgetItem *> til = ui->treeObjects->selectedItems();
	foreach(QTreeWidgetItem * i, til)
		sol << itemObject(i);
	view->scene()->selectObjects(sol);
	block_tree = false;
	checkActions();
}


void SceneTree::filter() {
	int types = 0;
	if (ui->actionFilter_node->isChecked()) types |= otNode;
	if (ui->actionFilter_mesh->isChecked()) types |= otMesh;
	if (ui->actionFilter_light->isChecked()) types |= otLight;
	if (ui->actionFilter_camera->isChecked()) types |= otCamera;
	if (types == 0) types = 0xFF;
	hidden_by_filter = 0;
	filterTree(ui->treeObjects->invisibleRootItem(), ui->lineFilter->text(), types);
	ui->treeObjects->invisibleRootItem()->setHidden(false);
	ui->labelCounts->setText(tr("%1 objects, %2 hide by filter").arg(obj_count).arg(hidden_by_filter));
}


void SceneTree::__objectDeleted(ObjectBase * o) {
	for (int i = 0; i < geo_items.size(); ++i)
		if (itemObject(geo_items[i]) == o) {
			geo_items.removeAt(i);
			--i;
		}
	for (int i = 0; i < cam_items.size(); ++i)
		if (itemObject(cam_items[i]) == o) {
			cam_items.removeAt(i);
			--i;
		}
}


void SceneTree::on_treeObjects_itemChanged(QTreeWidgetItem * item, int column) {
	if (block_tree) return;
	if (column == cName) {
		ObjectBase * o = itemObject(item);
		if (o) o->setName(item->text(cName));
	}
	if (column == cVis) {
		bool vis                     = item->checkState(cVis) == Qt::Checked;
		QList<QTreeWidgetItem *> til = ui->treeObjects->selectedItems();
		if (!til.contains(item)) {
			til.clear();
			til << item;
		}
		foreach(QTreeWidgetItem * ti, til) {
			// itemObject(ti)->setVisible(vis);
			itemObject(ti)->setVisible(vis);
		}
	}
}


void SceneTree::on_treeObjects_itemMoved(QTreeWidgetItem * item, QTreeWidgetItem * new_parent) {
	ObjectBase * co = itemObject(item);
	if (!co->hasParent()) return;
	// co->parent()->removeChild(co);
	if (new_parent == ui->treeObjects->invisibleRootItem()) {
		view->scene()->rootObject()->addChild(co);
	} else {
		ObjectBase * po = itemObject(new_parent);
		if (po) po->addChild(co);
	}
}


void SceneTree::on_actionAdd_node_triggered() {
	if (!view) return;
	ObjectBase * no = new ObjectBase();
	no->setName("node");
	view->scene()->addObject(no, insertTarget());
	view->scene()->selectObject(no);
}


void SceneTree::on_actionAdd_light_triggered() {
	if (!view) return;
	ObjectBase * no = new Light();
	no->setName("light");
	view->scene()->addObject(no, insertTarget());
	view->scene()->selectObject(no);
}


void SceneTree::on_actionAdd_camera_triggered() {
	if (!view) return;
	ObjectBase * no = new Camera();
	no->setName("camera");
	view->scene()->addObject(no, insertTarget());
	view->scene()->selectObject(no);
}


void SceneTree::on_actionClone_triggered() {
	if (!view) return;
	QList<QTreeWidgetItem *> sil = ui->treeObjects->selectedItems();
	ObjectBaseList col;
	foreach(QTreeWidgetItem * i, sil) {
		ObjectBase * o = itemObject(i);
		if (!o) continue;
		ObjectBase * no = o->clone();
		o->parent()->addChild(no);
		col << no;
	}
	view->scene()->selectObjects(col);
}


void SceneTree::on_actionGroup_triggered() {
	if (!view) return;
	ObjectBaseList sol = view->scene()->selectedObjects(true);
	ObjectBase * cp    = sol[0]->parent();
	ObjectBase * nr    = new ObjectBase();
	cp->addChild(nr);
	foreach(ObjectBase * o, sol)
		nr->addChild(o);
	view->scene()->selectObject(nr);
}


void SceneTree::on_actionTransfer_transform_to_children_triggered() {
	if (!view) return;
	ObjectBaseList sol = view->scene()->selectedObjects(true);
	foreach(ObjectBase * o, sol)
		o->transferTransformToChildren();
}


void SceneTree::on_actionMove_to_parent_triggered() {
	if (!view) return;
	ObjectBaseList sol = view->scene()->selectedObjects(true);
	foreach(ObjectBase * o, sol) {
		auto * old_p = o->parent();
		if (!old_p) continue;
		auto * new_p = old_p->parent();
		if (!new_p) continue;
		old_p->removeChild(o);
		new_p->addChild(o);
	}
}


void SceneTree::on_actionActive_camera_triggered() {
	if (!view) return;
	ObjectBase * o = view->scene()->selectedObject();
	if (!o) return;
	if (o->type() != ObjectBase::glCamera) return;
	view->setCamera((Camera *)o);
	cameraChanged();
}


void SceneTree::on_actionDefault_camera_triggered() {
	if (!view) return;
	view->setDefaultCamera();
	cameraChanged();
}


void SceneTree::on_actionSelect_parent_triggered() {
	if (!view) return;
	ObjectBaseList sol = view->scene()->selectedObjects(true);
	QSet<ObjectBase *> nsl;
	foreach(ObjectBase * o, sol) {
		ObjectBase * po = o->parent();
		if (po != view->scene()->rootObject()) o = po;
		nsl << o;
	}
	view->scene()->selectObjects(nsl.values());
}


void SceneTree::on_actionSelect_by_mesh_triggered() {
	view->scene()->selectObjectsByMesh();
}


void SceneTree::on_actionSelect_by_material_triggered() {
	view->scene()->selectObjectsByMaterial();
}


void SceneTree::removeObjects() {
	if (!view) return;
	QList<QTreeWidgetItem *> sil = ui->treeObjects->selectedItems();
	foreach(QTreeWidgetItem * i, sil) {
		ObjectBase * o = itemObject(i);
		if (o->isSelected(true)) view->scene()->clearSelection();
		if (o) delete o;
	}
	qDeleteAll(sil);
}


void SceneTree::focusObjects() {
	if (!view) return;
	if (!view->camera()) return;
	Box3D bb;
	ObjectBaseList ol = view->selectedObjects();
	foreach(ObjectBase * o, ol) {
		o->calculateBoundingBox();
		bb |= o->boundingBox();
	}
	view->focusOn(bb);
}


void SceneTree::objectsTreeChanged() {
	int vpos = ui->treeObjects->verticalScrollBar()->value();
	expanded_.clear();
	geo_items.clear();
	cam_items.clear();
	rememberExpanded(ui->treeObjects->invisibleRootItem());
	block_tree = true;
	ui->treeObjects->clear();
	block_tree = false;
	if (!view) return;
	block_tree = true;
	obj_count  = 0;
	makeObjetTree(view->scene()->rootObject(), ui->treeObjects->invisibleRootItem());
	--obj_count;
	restoreExpanded(ui->treeObjects->invisibleRootItem());
	block_tree = false;
	filter();
	QApplication::processEvents();
	ui->treeObjects->verticalScrollBar()->setValue(vpos);
}
