#include <QDoubleSpinBox>
#include <QFileDialog>
#include <QTreeView>

#include "xmlModel.h"
#include "tabWidget.h"
#include "mainwindow.h"


QString modeToString(SWITCH);

/*
	Delegate implementations
	========================
*/
CustomXmlDelegate::CustomXmlDelegate(int column, QObject *parent) : QItemDelegate(parent), parentObj(parent)
{
	valueColumn = column;
}

QWidget *CustomXmlDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
	if (index.column() == valueColumn) {
		if (index.model()->data(index, Qt::StatusTipRole).toString().split(",", QString::SkipEmptyParts)[0] == "path") {
			QComboBox *edit = new QComboBox(parent);
			edit->installEventFilter(const_cast<CustomXmlDelegate*>(this));
			edit->setEditable(true);
			return edit;
		} else {
			QDoubleSpinBox *edit = new QDoubleSpinBox(parent);
			edit->installEventFilter(const_cast<CustomXmlDelegate*>(this));
// RANGES / wertebereiche
			//global ranges
			QStringList range = index.model()->data(index, Qt::StatusTipRole)
						.toString().split(",", QString::SkipEmptyParts);
			if (range.size() > 1) {
				int range_low = range[0].toInt();
				int range_high = range[1].toInt();
				edit->setRange(range_low, range_high);
			}
			edit->setDecimals(2);
			connect(edit, SIGNAL(editingFinished()), this, SLOT(commitAndCloseSpinEditor()));
			
			return edit;
		} // if
	} else {
		return QItemDelegate::createEditor(parent, option, index);
	} // if
}


void CustomXmlDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
	if (index.column() == valueColumn) {
		QString value = index.model()->data(index, Qt::DisplayRole).toString();
		QString attr = index.model()->data(index, Qt::StatusTipRole).toString();
		if (attr == "path") {			
			QComboBox *comboBox = qobject_cast<QComboBox*>(editor);
			comboBox->insertItem(0,value);
			
			// extract folder path of the given value and add this folders files to combobox
			int lastSlashPos = value.lastIndexOf(QDir::convertSeparators("/"),value.size()-1);
			value.truncate(lastSlashPos);
			QDir *comboPath = new QDir(QDir::currentPath() + QDir::convertSeparators("/") + value);
			comboPath->setSorting(QDir::Time);
			if (comboPath->exists()) {
				QStringList values = comboPath->entryList(QDir::Files);
				for (int i = 0; i < values.size() && i < 10; i++) {
					comboBox->addItem(value + QDir::convertSeparators("/") + values[i]);
				} // for
			} // if				
			comboBox->addItem("Browse");
		} else {
			double value = index.model()->data(index, Qt::DisplayRole).toDouble();
			QDoubleSpinBox *spinBox = static_cast<QDoubleSpinBox*>(editor);
			spinBox->setValue(value);
		} // if
	} else {
		QItemDelegate::setEditorData(editor, index);
	} // if
}


void CustomXmlDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
	if (index.column() == valueColumn) {
		if (index.model()->data(index, Qt::StatusTipRole).toString().split(",", QString::SkipEmptyParts)[0] == "path") {
			QComboBox *comboBox = qobject_cast<QComboBox*>(editor);
			QString value = comboBox->currentText();
			if (value == "Browse") {
				QFileDialog *fileDialog = new QFileDialog();
				fileDialog->setModal(true);
				fileDialog->setReadOnly(false);
				fileDialog->setFileMode(QFileDialog::AnyFile);
				fileDialog->setDirectory(QDir::currentPath());
				fileDialog->setFilter( "Xml (*.xml) dat (*.dat)" );
				value = fileDialog->getExistingDirectory();
			}
// 			QMessageBox::information(0,"",QString("%1") .arg(value));
			model->setData(index, value);
		} else {
			QDoubleSpinBox *spinBox = static_cast<QDoubleSpinBox*>(editor);
			spinBox->interpretText();
			double value = spinBox->value();
			model->setData(index, value);
		}
	} else {
		QItemDelegate::setModelData(editor, model, index);
	} // if
}

/*
void CustomXmlDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const
{
	editor->setGeometry(option.rect);
}
*/

void CustomXmlDelegate::commitAndCloseSpinEditor()
{
	QDoubleSpinBox *editor = qobject_cast<QDoubleSpinBox *>((sender()));
	emit commitData(editor);
	emit closeEditor(editor);
}

/*
	Model implementations
	=====================
*/
QDomNode xmlFindTag(QString needle, QDomNode hayStack) 
{// parse dom-tree (hayStack) for needle !!no errorcode 
	QDomNode dummy;
	while (!hayStack.isNull() && hayStack.nodeName() != needle)
	{
		if (hayStack.hasChildNodes()) {
			dummy = xmlFindTag(needle, hayStack.firstChild());
			if (dummy.nodeName() == needle) return dummy; // if
		} // if
		hayStack = hayStack.nextSibling();
	} // while
	
	return hayStack;
}

XmlModel::XmlModel() {

}

XmlModel::XmlModel(QDomDocument document, SWITCH refMode, QObject *parentObj) 
			: QAbstractItemModel(parentObj), domDocument(document), mode(refMode), parentObj(parentObj)
{
	disableWidgets = 0;
	rootItem = new DomItem(domDocument, 0);
	QDomNode veryRoot = rootItem->node(); // first node in xml file
	veryRoot = xmlFindTag(QString("satpred"), veryRoot); // is it satpred?
	
	refTag = modeToString(refMode);
	if (mode == Unknown) {
		disableWidgets = 1; // threads shall not create widgets
		refTag = veryRoot.attributes().namedItem("job").nodeValue(); // determine which parent to evaluete (ie track, global, area..)
	} // if
	
 	if (veryRoot.nodeName() == "satpred") {
		veryRoot = xmlFindTag(refTag, veryRoot); // scope on wished mode, for example: track part of xml file becomes new root
		global = veryRoot.attributes().namedItem("global").nodeValue(); // catch global file name
		if (veryRoot.nodeName() == refTag) {
			delete rootItem;
			rootItem = new DomItem(veryRoot,0);
		} else if (!disableWidgets) {// threads shall not create widgets
			QMessageBox::warning(0,"xmlModel.cpp", 
				tr("Kann gewuenschten Tag - %1 - nicht finden")
				.arg(refTag));
		}// if
	} // if
	
	if (refMode != Global && refMode != Geos && refMode != Pseuds && refMode != Unknown) {
		// load linked global config file in global settings panel
		QString linkedGlobal = veryRoot.attributes().namedItem("global").nodeValue();		
		QWidgetList widgetList = QApplication::allWidgets();
		int mainWindowPos = -1;
		for (int i= 0; i < QApplication::allWidgets().size(); i++) {
			QWidget *mainWindow = widgetList[i];
			if (mainWindow != 0 && mainWindow->objectName() == "mainWin") {
				mainWindowPos = i;
				break;
			} // if
		}
		if (mainWindowPos > -1) {
			MainWindow *mainWindow = qobject_cast<MainWindow*>(QApplication::allWidgets()[mainWindowPos]);
			TabWidget *rPane = qobject_cast<TabWidget*>(mainWindow->rightPane); // rPane is Global Settings Box.
			if (rPane != 0 && linkedGlobal != "" && linkedGlobal != "res/settings/default.xml") {
				rPane->updateTreeView(linkedGlobal);
			} // if
		} // if		
	} // if
}

int XmlModel::columnCount(const QModelIndex &/*parent*/) const
{
	return 3; // 3 columns -> 0: name 1: value 2: comment
}

bool XmlModel::setData(const QModelIndex & index, const QVariant & value, int role)
{	
	if (index.isValid() && role == Qt::EditRole && index.column() == 1) { // only value column is editable
	
		DomItem *item = static_cast<DomItem*>(index.internalPointer()); // get pointer		
		QDomElement valueTag = domDocument.createElement("value"); // create new dom element
		valueTag.setAttribute("range", index.model()->data(index, Qt::StatusTipRole).toString()); // !set type #> segmentation fault
		
		QDomText text = domDocument.createTextNode(value.toString()); // set entered value
		
		item->node().appendChild(valueTag); // ->
		valueTag.appendChild(text);		// set node.valueTag.text()
		
		item->node().replaceChild(valueTag,item->node().firstChild()); // kick old node and insert new
		emit dataChanged (index, index); // tell view class that sth changed	
		return true;
	} // if
	return false;
}

QVariant XmlModel::data(const QModelIndex &index, int role) const
{
	if (!index.isValid())
		return QVariant();

	if (role == Qt::TextAlignmentRole && index.column() == 1)
		return int(Qt::AlignCenter | Qt::AlignVCenter);	// center value column
	
	if (role == Qt::DisplayRole || role == Qt::StatusTipRole || (role == Qt::ToolTipRole && index.column() == 2)) {
	// (role == Qt::ToolTipRole && index.column() == 2) -> view comments as tooltip as well 
		DomItem *item = static_cast<DomItem*>(index.internalPointer());
	
		QDomNode node = item->node(); // get node
	
		QDomNode child = node.firstChild(); // first child to evaluate
		QStringList attributes;
		QDomNamedNodeMap attributeMap = node.attributes(); // get attributes
		
		switch (index.column()) {
		case 0:
			return attributeMap.namedItem("name").nodeValue(); // first column is name = attribute of this node
		case 1:
			while (!child.isNull() && child.nodeName() != "value") // check childs for value node -> 2nd col
			{
				child = child.nextSibling();
			}
			
			if (role == Qt::StatusTipRole) {
			// the statustiprole is used to determine, which type
			// of data this field holds. thus we may have differing ranges or delegates
				QDomNamedNodeMap valueAttributeMap = child.attributes(); // get attributes
				if (!valueAttributeMap.namedItem("range").isNull())
					return valueAttributeMap.namedItem("range").nodeValue();
				else 
					return "unknown";
			} // if
			return child.toElement().text(); // return value of value node
		case 2:
			while (!child.isNull() && child.nodeName() != "comment") // check child for comment node
			{
				child = child.nextSibling();
			}
			return child.toElement().text(); // return value of comment node
		default:
			return QVariant();
		} // switch
	} // if
	return QVariant();
} 

Qt::ItemFlags XmlModel::flags(const QModelIndex &index) const
{
	if (!index.isValid())
		return Qt::ItemIsEnabled;

	if(index.column() == 1) // only value col editable
		return Qt::ItemIsEnabled | Qt::ItemIsSelectable  | Qt::ItemIsEditable;
	
	return Qt::ItemIsEnabled;
}

QVariant XmlModel::headerData(int section, Qt::Orientation orientation, int role) const
{
	if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
		switch (section) {
			case 0:
			return tr("Name");
			case 1:
			return tr("Wert");
			case 2:
			return tr("Kommentar");
			default:
			return QVariant();
		}//switch
	}//if

	return QVariant();
}

QModelIndex XmlModel::index(int row, int column, const QModelIndex &parent) const
{
	DomItem *parentItem;

	if (!parent.isValid())
		parentItem = rootItem;
	else
		parentItem = static_cast<DomItem*>(parent.internalPointer());

	DomItem *childItem = parentItem->child(row);
	if (childItem && childItem->node().nodeName() == "entry")
		return createIndex(row, column, childItem); 
	else
		return QModelIndex();
}

QModelIndex XmlModel::parent(const QModelIndex &child) const
{
	if (!child.isValid())
		return QModelIndex();

	DomItem *childItem = static_cast <DomItem*> (child.internalPointer());
	DomItem *parentItem = childItem->parent();

	if (!parentItem || parentItem == rootItem)
		return QModelIndex();

	return createIndex(parentItem->row(), 0, parentItem);
}

int XmlModel::rowCount(const QModelIndex &parent) const
{
	DomItem *parentItem;

	if (!parent.isValid())
		parentItem = rootItem;
	else
		parentItem = static_cast<DomItem*>(parent.internalPointer());

	int count = parentItem->node().childNodes().count();
	QDomNode child = parentItem->node().firstChild();
	while (!child.isNull())
	{
		if (child.nodeName() == "value" || child.nodeName() == "comment")
			count--;
		child = child.nextSibling();
	}//while
	return count;
}


bool XmlModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{// 
	if (action == Qt::IgnoreAction) return true; // if
	
	if (!data->hasFormat("application/x-qabstractitemmodeldatalist")) return false; // if

	if (column > 0) return false; // if
	
	int beginRow;
	
	if (row != -1) 
		beginRow = row;
	else if (parent.isValid())
		beginRow = parent.row();
	else
		beginRow = rowCount(QModelIndex());
	// if
	
	QByteArray encodedData = data->data("application/x-qabstractitemmodeldatalist");
	QDataStream stream (&encodedData, QIODevice::ReadOnly);
	QStringList newItems;
	int rows = 0;
	
	while (!stream.atEnd()) {
		QString text;
		stream >> text;
		newItems << text;
		++rows;
	} // while
	
	newPath = newItems[0] + "/" + newItems[1]; // [0] is relative path (to working dir) [1] is filename
	connect(this, SIGNAL(newXmlPath(QString)), parentObj, SLOT(updateTreeView(QString)));
	emit newXmlPath(newPath);
	return true;
}

bool XmlModel::addRow(const QModelIndex &parent) 
{
// 	beginInsertRows(QModelIndex(),rowCount(),rowCount());

// 	int row = rowCount();
// 	QDomNode node = rootItem->node();
// 	rootItem->node().appendChild(node);
	
// 	endInsertRows();
	return false;
}



bool XmlModel::removeRow(int row, const QModelIndex &parent)
{
	
	if (row < rowCount(QModelIndex())) {
		beginRemoveRows(parent, row, row);
		QModelIndex corIndex = index(row, 0, QModelIndex());
		
		DomItem *item = static_cast<DomItem*>(corIndex.internalPointer()); // get pointer
		item->parent()->removeChild(row);
		rootItem = item->parent();
		delete item;

		endRemoveRows();
		
		return true;
	} // if
	
	return false;
}


// some functions for save()
void XmlModel::writeEntry(XmlWriter *xw, const QModelIndex &writeIndex) const
{
	
	QString name = writeIndex.data(Qt::DisplayRole).toString();
	QString value = writeIndex.sibling(writeIndex.row(), 1).data(Qt::DisplayRole).toString();
	QString type = writeIndex.sibling(writeIndex.row(), 1).data(Qt::StatusTipRole).toString();
	QString comment = writeIndex.sibling(writeIndex.row(), 2).data(Qt::DisplayRole).toString();
	
	xw->writeOpenTag("entry", AttrMap("name", name));
	AttrMap attrs;
    	attrs.insert("range", type);
	
	// check for childs "entry" -> recursive call
	DomItem *parent = static_cast<DomItem*>(writeIndex.sibling(writeIndex.row(),0).internalPointer());
	int count = parent->node().childNodes().count();	
	if (count > 2) {
		QDomNode child = parent->node().firstChild();
		int j = 0;
		while (!child.isNull()) // check childs for value node -> 2nd col
		{
			if (child.nodeName() == "entry") {
				writeEntry(xw, writeIndex.child(j,0));
			} // if
			j++;
			child = child.nextSibling();
		} // while
	} // if
	// <!-- check for childs "entry"
	
	xw->writeTaggedString("value", value, attrs);
	xw->writeTaggedString("comment", comment);
	xw->writeCloseTag("entry");
}
	
bool XmlModel::save(QString relPath, SWITCH mode) // relative path
{// save model as .xml file
	QFile file(relPath);	
	QString modeStr = modeToString(mode);	
	
	if (!file.open(QIODevice::WriteOnly)) return false; // error opening file
	XmlWriter *xw = new XmlWriter(&file);	 
	xw->setAutoNewLine(true);
	xw->writeRaw("<?xml version=\"1.0\"?>");
	xw->newLine();
	
	//xw->writeOpenTag("satpred", AttrMap("job", modeStr));	
	xw->writeOpenTag("satpred", AttrMap("job", modeStr));
	
	if (mode != Global && mode != Geos && mode != Pseuds) { // insert link to gloabal file
		QString globalSettingsFile = "res/settings/default.xml";
		// find mainWin in application (mainwindow)
		QWidgetList widgetList = QApplication::allWidgets();
		int mainWindowPos = -1;
		for (int i= 0; i < QApplication::allWidgets().size(); i++) {
			QWidget *mainWindow = widgetList[i];
			if (mainWindow != 0 && mainWindow->objectName() == "mainWin") {
				mainWindowPos = i;
				break;
			} // if
		}

		if (mainWindowPos > -1) {
			MainWindow *mainWindow = qobject_cast<MainWindow*>(QApplication::allWidgets()[mainWindowPos]);
			TabWidget *rPane = qobject_cast<TabWidget*>(mainWindow->rightPane);
			if (rPane != 0) {
				globalSettingsFile = rPane->xmlPath;
			} // if
		} // if
		int choice = 0;
		if (global != globalSettingsFile) {
			choice = QMessageBox::question(
				0,
				tr("Andere GlobalSettings-Datei verlinkt."),
				tr("In der Datei ist eine andere globale Datei verlinkt als nun ausgewählt."
				"Welche Datei soll verlinkt werden?"),
				tr("&Alt"), tr("&Neu"),
				QString(), 0, 1);
		// 0 = alt 1 = neu
		} // if 
		if (choice) {
			xw->writeOpenTag(modeStr, AttrMap("global", globalSettingsFile));
		} else {
			xw->writeOpenTag(modeStr, AttrMap("global", global)); // use object member (loaded globalpath in constructor)
		} // if
		
	} else {
		xw->writeOpenTag(modeStr);
	} // if
		
	for(int i = 0; i < this->rowCount(); i++) {
		writeEntry(xw, this->index(i,0,QModelIndex()));
	} // for
	xw->writeCloseTag(modeStr);
	xw->writeCloseTag("satpred");

	return true;
}

XmlModel::~XmlModel()
{
	delete rootItem;
}



/* 
	DomItem implementations 
	=======================
*/

DomItem::DomItem(QDomNode &node, int row, DomItem *parent)
{
	domNode = node;
	// Record the item's location within its parent.
	rowNumber = row;
	parentItem = parent;
}

DomItem::~DomItem()
{
	QHash<int,DomItem*>::iterator it;
	for (it = childItems.begin(); it != childItems.end(); ++it)
		delete it.value();
}

QDomNode DomItem::node() const
{
	return domNode;
}

DomItem *DomItem::parent()
{
	return parentItem;
}

DomItem *DomItem::child(int i)
{
	if (childItems.contains(i))
		return childItems[i];

	if (i >= 0 && i < domNode.childNodes().count()) {
		QDomNode childNode = domNode.childNodes().item(i);
		DomItem *childItem = new DomItem(childNode, i, this);
		childItems[i] = childItem;
		return childItem;
	}
	return 0;
}

int DomItem::row()
{
	return rowNumber;
}


void DomItem::removeChild(int pos)
{
	childItems.remove(pos);
}


