/***************************************************************************
 *   Copyright (C) 2004, 2005 Thomas Nagy                                  *
 *   tnagy2^8@yahoo.fr                                                     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License version 2        *
 *   as published by the Free Software Foundation (see COPYING)            *
 *                                                                         *
 *   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.                          *
 ***************************************************************************/

#include <assert.h>
#include <pwd.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

#include <qdir.h>
#include <qstring.h>
#include <qstringlist.h>
#include <qvaluelist.h>
#include <qtextstream.h>
#include <qfile.h>
#include <qregexp.h>

#include <karchive.h>
#include <ktar.h>
#include <klocale.h>
#include <ktempfile.h>
#include <ktempdir.h>
#include <kmessagebox.h>
#include <kio/netaccess.h>
#include <kurl.h>
#include <ktempdir.h>
#include <kdebug.h>

#include "settings.h"
#include "DBase.h"
#include "DDataItem.h"
#include "DGuiView.h"
#include "DDataControl.h"
#include "DSpell.h"
#include "FFParser.h"
#include "DissertParser.h"

static const int document_version = 7;

DDataControl::DDataControl(QObject * parent, const char * name) : QObject(parent, name)
{
	m_lastid = 0;
	m_spell = NULL;
	m_undoredo_locked = false;
	
	m_reflist.setAutoDelete(true);

	/// author
	m_fname = Settings::fname();
	m_sname = Settings::sname();
	m_email = Settings::email();
	m_company = Settings::company();

	if (!m_fname.length() && !m_sname.length())
	{
		struct passwd *pw = ::getpwuid(getuid());
		if (pw)
		{
			m_sname = QString::fromLocal8Bit( pw->pw_gecos );
			Settings::setSname( m_sname );
		}
	}

	/// pic preview size
	m_pixSize = Settings::pixSize();
	m_col_background = Settings::col_background();
	m_col_link = Settings::col_link();
	m_col_ref = Settings::col_ref();

	m_canvasFont = Settings::canvasFont();
	m_picturesOnCanvas = Settings::picturesOnCanvas();

	m_showDirectionSize = Settings::showDirectionSize();

	m_linkstyle = Settings::linkStyle();

	//internal connections
	connect(this, SIGNAL(itemChanged(int)), this, SLOT(docChanged()));
	connect(this, SIGNAL(itemCreated(int)), this, SLOT(docChanged()));
	connect(this, SIGNAL(itemRemoved(int)), this, SLOT(docChanged()));

	m_fileholder = new KTempDir();
}

DDataControl::~DDataControl()
{
	m_fileholder->unlink();

	delete m_spell;
	//delete m_fileholder;
}

void DDataControl::updateSettings()
{
	emit settingsChanged();
}

DDataItem* DDataControl::createItemWithId(int id)
{
	DDataItem* item = NULL;

	//kdDebug()<<"DDataControl::createItemWithId "<<id<<endl;
	if (isRegistered(id))
	{
		kdWarning()<<"BUG: attempted to create an item that already exists!"<<endl;
		return NULL;
	}
	item = new DDataItem(this, id);
	registerItem(item);

	return item;
}

void DDataControl::notifySpecial(DDataItem *item)
{
	emit itemCreated( item->Id() );
	emit notifyItemModified(item->Id());
	setItemSelected(item->Id(), NULL);
	docChanged();
}

int DDataControl::createItem(int parentid, int optionaldefaultid)
{
	DDataItem* item = NULL;

	if (optionaldefaultid != DItem::NOITEM) m_lastid = optionaldefaultid;
	while ( isRegistered(m_lastid) ) m_lastid++;

	item = createItemWithId(m_lastid);
	if (!item) kdWarning()<<"BUG DDataControl::createItem createItemWithId returned NULL"<<endl;
	
	addDelta( new DDeltaCreate(this, m_lastid) );

	emit itemCreated( item->Id() );
	if (parentid != DItem::NOITEM)
	{
		linkItems(parentid, item->Id());
		if (Settings::inherit())
		{
			DDataItem *parent = dataItem(parentid);
			item->m_defaultFont = parent->m_defaultFont;
			if (parent->colorScheme() == Settings::EnumColorMode::custom_)
			{
				item->setCustomColors( parent->fillColor(), parent->outlineColor(), parent->textColor() );
			}
			else
			{
				item->setColorScheme( parent->colorScheme() );
			}
			emit notifyItemModified(item->Id());
		}
	}

	setItemSelected(item->Id(), NULL);
	docChanged();

	return item->Id();
}

void DDataControl::linkItems(int parent, int child)
{
	// kdWarning()<<"call to DDataControl::linkItems"<<endl;

	// link only valid items
	if (parent == DItem::NOITEM || child == DItem::NOITEM)
	{
		kdWarning()<<"BUG in DDataControl::linkItems : "<<parent<<" "<<child<<endl;
		return;
	}

	if (parent == child) return;

	// forbid cycles
	int iter = parent;
	while (iter != DItem::NOITEM)
	{
		if (iter == child)
		{
			emit tip( i18n("Cycles are forbidden!") );
			return;
		}

		iter = dataItem(iter)->Parent();
	}

	// for linking, the child must have no parent
	if ( dataItem(child)->Parent() == DItem::NOITEM )
	{
		dataItem(parent)->addChild(child);
		dataItem(child)->setParent(parent);

		addDelta( new DDeltaLink(this, child, parent) );
		
		emit itemChanged(parent);
		emit itemChanged(child);
	}
	else
	{
		emit tip( i18n("Object has already a parent!") );
	}
}

void DDataControl::unlinkItems(int id1, int id2)
{
	//kdWarning()<<"unlink "<<id1<<" "<<id2<<endl;
	if (id1 == id2) return;
	if (id1 == DItem::NOITEM || id2 == DItem::NOITEM) return;

	bool changed = false;

	if (!dataItem(id1))
	{
		dataItem(id2)->removeChild(id1);
	}
	else if (!dataItem(id2))
	{
		dataItem(id1)->removeChild(id2);
	}
	else if (dataItem(id1)->Parent() == id2)
	{
		changed = true;
		dataItem(id1)->setParent(DItem::NOITEM);
		dataItem(id2)->removeChild(id1);
		addDelta( new DDeltaLink(this, id1, id2, true) );
	}
	else if (dataItem(id2)->Parent() == id1)
	{
		changed = true;
		dataItem(id2)->setParent(DItem::NOITEM);
		dataItem(id1)->removeChild(id2);
		addDelta( new DDeltaLink(this, id2, id1, true) );
	}

	if (changed)
	{
		emit itemChanged(id1);
		emit itemChanged(id2);
	}
}

void DDataControl::removeItem(int id)
{
	kdDebug()<<"removeItem "<<id<<endl;
	if (!isRegistered(id) ) return;

	disconnectItem(id);
	addDelta( new DDeltaCreate(this, id, true) );
	unregisterItem(id);
	emit itemRemoved( id );
	emit itemSelected( DItem::NOITEM, NULL );

	docChanged();
}

void DDataControl::disconnectItem(int id)
{
	if (!isRegistered(id)) return;

	// remove the references
	killRefs(id);
	// disconnect the children
	killChildren(id);
	// disconnect the parent
	setOrphan(id);

	docChanged();
}

void DDataControl::killRefs(int id)
{
	if (!isRegistered(id)) return;
	
	QPtrList<QPoint>::iterator it = m_reflist.begin();
	for (; it != m_reflist.end(); ++it)
	{
		QPoint *p = *it;
		if (p->x() == id)
		{
			requestRefChange(p->x(), p->y(), false);
			m_reflist.remove(p);

		}
		else if (p->y() == id)
		{
			requestRefChange(p->x(), p->y(), false);
			m_reflist.remove(p);
		}
	}
}

void DDataControl::setOrphan(int child)
{
	if (child == DItem::NOITEM) return;
	if (!isRegistered(child)) return;
	int parent = dataItem(child)->Parent();
	if (parent == DItem::NOITEM) return;
	if (child == parent) return;
	unlinkItems(child, parent);
}

void DDataControl::killChildren(int id)
{
	if (!isRegistered(id)) return;

	DDataItem* item = dataItem(id);
	while (item->countChildren()>0)
	{
		unlinkItems(id, item->childNum(0));
	}
}

void DDataControl::killFamily(int id)
{
	while (dataItem(id)->countChildren() > 0) killFamily( dataItem(id)->childNum(0) );
	setOrphan(id);
}

DDataItem* DDataControl::dataItem(int id) const
{
	return (DDataItem*) DBase::Item(id);
}

void DDataControl::clearDocument()
{
	// just to make sure :)
	checkConsistency();

	// delete all items
	QValueList<int>::iterator it;
	QValueList<int> allitems = m_map.keys();
	for ( it = allitems.begin(); it != allitems.end(); ++it )
	{
		removeItem(*it);
	}

	// clear the documents added
	m_fileholder->unlink();
	delete m_fileholder;
	m_fileholder = new KTempDir();
}

int DDataControl::compare(int id1, int id2) const
{
	if (id1 == id2) return 0;
	DDataItem *item1 = dataItem(id1);
	DDataItem *item2 = dataItem(id2);
	if (!item1 || !item2) return 0;

	if (item1->Parent() != item2->Parent()) return 0;

	if (item1->Parent() == DItem::NOITEM && item2->Parent() == DItem::NOITEM)
	{
		if (item1->countChildren() >= item2->countChildren())
		{
			if (item1->countChildren() > item2->countChildren()) return -1;
			return 0;
		}
		return 1;
	}

	DDataItem *dataitem_parent = dataItem(item1->Parent());
	if (dataitem_parent->childIdx(id1) > dataitem_parent->childIdx(id2)) return -1;
	else return 1;
}

void DDataControl::notifyChildren(int id)
{
	DDataItem* item = dataItem(id);
	if (item)
	{
		// update the children positions
		for (unsigned int i = 0; i<item->countChildren(); i++)
		{
			emit itemChanged(item->childNum(i));
		}
	}
}

void DDataControl::printTree(QTextStream & s)
{
	s.setEncoding( QTextStream::UnicodeUTF8 );

	s<<QString("<?xml version=\"1.0\" encoding=\"utf8\"?>\n");
	s<<"<kdissertdoc>\n";
	s<<"<version>"<<document_version<<"</version>\n";

	s<<"<meta ";
	s<<"project=\""<<DDataItem::protectXML(m_project)<<"\" ";
	s<<"path=\""<<DDataItem::protectXML(m_path)<<"\" ";
	s<<"fname=\""<<DDataItem::protectXML(m_fname)<<"\" ";
	s<<"sname=\""<<DDataItem::protectXML(m_sname)<<"\" ";
	s<<"email=\""<<DDataItem::protectXML(m_email)<<"\" ";
	s<<"company=\""<<DDataItem::protectXML(m_company)<<"\" ";

	s<<"pixsize=\""<<m_pixSize<<"\" ";
	s<<"colbackground=\""<<m_col_background.name()<<"\" ";
	s<<"collink=\""<<m_col_link.name()<<"\" ";
	s<<"colref=\""<<m_col_ref.name()<<"\" ";

	s<<"canvasfont=\""<<m_canvasFont.toString()<<"\" ";
	s<<"picturesoncanvas=\""<<(int) m_picturesOnCanvas<<"\" ";

	s<<"generator_lasturl=\""<<m_generator_lasturl<<"\" ";
	s<<"generator_lastgen=\""<<m_generator_lastgen.join(",")<<"\" ";
	
	s<<"showdirectionsize=\""<<m_showDirectionSize<<"\" ";
	s<<"linkstyle=\""<<m_linkstyle<<"\" />\n";

	QMapIterator<int, DItem*> it;
	for (it = m_map.begin(); it != m_map.end(); ++it)
	{
		DDataItem* item = (DDataItem*) (*it);
		if (item) item->printSelf(s);
		else kdWarning()<<"BUG : in DDataControl::printTree"<<endl;
	}

	s<<"</kdissertdoc>\n";
}

bool DDataControl::loadFromFile(const KURL & url, bool import, bool nogui)
{
	int idval = m_lastid+1;

	QString filename = url.url();
	bool safe = false;
	if (filename.endsWith(".kdi", false)) safe=true;
	if (!safe) if (filename.endsWith(".kno", false)) safe=true;
	if (!safe) if (filename.endsWith(".mm", false)) safe=true;
	
	if (import && ! filename.endsWith(".kdi", false) ) safe=false;

	if (!safe)
	{
		kdDebug()<<"open kdissert documents failed "<<filename<<endl;
		return false;
	}

	if (!import) clearDocument();

	QString tmpFile;
	if (! KIO::NetAccess::download(url, tmpFile, NULL))
	{
		QString text;

		if ( url.isLocalFile() )
			text = i18n( "Unable to find the kdissert file %1" );
		else
			text = i18n( "Unable to download the kdissert file! "
					"Please check that the address %1 is correct." );

		if (nogui) kdFatal()<<text.arg( url.prettyURL() )<<endl;
		else       KMessageBox::sorry( NULL, text.arg( url.prettyURL() )  );

		docNotChanged();

		return false;
	}


	if (filename.endsWith(".kdi", false))
	{
		KTar arch(tmpFile);
		if (! arch.open(IO_ReadOnly) )
		{
			QString text = i18n("Unable to open the kdissert file %1!");
			KIO::NetAccess::removeTempFile( tmpFile );

			if (nogui) kdFatal()<<text.arg( url.prettyURL() )<<endl;
			else       KMessageBox::sorry( NULL, text.arg( url.prettyURL() )  );

			docNotChanged();
			return false;
		}

		const KArchiveDirectory *archiveDir = arch.directory();
		QStringList lst = archiveDir->entries();

		// Find the main document or return false
		bool cond = false;
		for (unsigned int i=0; i<lst.count(); i++)
		{
			if (lst[i] == "maindoc.xml")
			{
				cond = true;
				break;
			}
		}

		if (!cond)
		{
			QString text = i18n("Unable to parse the kdissert file %1!");
			KIO::NetAccess::removeTempFile( tmpFile );

			if (nogui) kdFatal()<<text.arg( url.prettyURL() )<<endl;
			else       KMessageBox::sorry( NULL, text.arg( url.prettyURL() )  );

			docNotChanged();
			return false;
		}

		const KArchiveEntry* entry = archiveDir->entry("maindoc.xml");
		if (!entry->isFile())
		{
			QString text = i18n("The kdissert file %1 does not seem to be a valid kdissert file!");
			KIO::NetAccess::removeTempFile( tmpFile );

			if (nogui) kdFatal()<<text.arg( url.prettyURL() )<<endl;
			else       KMessageBox::sorry( NULL, text.arg( url.prettyURL() )  );

			docNotChanged();
			return false;
		}

		KArchiveFile* maindocentryfile = (KArchiveFile*) entry;
		KTempDir tmpdir;
		maindocentryfile->copyTo(tmpdir.name());

		// Parse the maindoc file now
		QString tmpdocname = tmpdir.name() + maindocentryfile->name();
		QFile maindocfile(tmpdocname);

		DissertParser handler(this, idval, import);
		QXmlInputSource source( &maindocfile );
		QXmlSimpleReader reader;
		reader.setContentHandler( &handler );

		bool result = reader.parse( &source , true );
		if (!result)
		{
			QString text = i18n("The kdissert file %1 does not seem to be a valid kdissert file!");

			if (!import) clearDocument();
			KIO::NetAccess::removeTempFile( tmpFile );
			if (!import) tmpdir.unlink();

			if (nogui) kdFatal()<<text.arg( url.prettyURL() )<<endl;
			else       KMessageBox::sorry( NULL, text.arg( url.prettyURL() )  );

			docNotChanged();
			return false;
		}

		// Load the cached documents 
		// TODO: pictures for the moment, can be other things in the future

		QRegExp rx("^pic.\\d+....$");
		for (unsigned int i=0; i<lst.count(); i++)
		{
			if (rx.search(lst[i]) == 0)
			{
				QString val = lst[i];
				val.remove(0, 4);
				val = val.left(lst[i].length()-8);
				int id = val.toInt() + idval;

				DDataItem* item = dataItem(id);
				if (item)
				{
					entry = archiveDir->entry(lst[i]);
					if (entry->isFile())
					{
						KArchiveFile* picfile = (KArchiveFile*) entry;
						picfile->copyTo(tmpdir.name());
						item->loadPix(tmpdir.name() + lst[i]);
					}
				}
				else
				{
					kdWarning() <<"BUG: a pix item is missing"<<endl;
				}
			}
		}

		tmpdir.unlink();
	}
	else if (filename.endsWith(".mm", false))
	{
		QFile maindocfile(tmpFile);
		FFParser handler(this);
		QXmlInputSource source( &maindocfile );
		QXmlSimpleReader reader;
		reader.setContentHandler( &handler );

		bool result = reader.parse( &source , true );
		if (!result)
		{
			QString text = i18n("The mindmap file %1 does not seem to be a valid freemind file!");

			clearDocument();
			KIO::NetAccess::removeTempFile( tmpFile );

			if (nogui) kdFatal()<<text.arg( url.prettyURL() )<<endl;
			else       KMessageBox::sorry( NULL, text.arg( url.prettyURL() )  );

			docNotChanged();
			return false;
		}
		docChanged();
	}
	else if (filename.endsWith("kno", false))
	{
		QFile maindocfile(tmpFile);

		if (!maindocfile.open(IO_ReadOnly)) 
		{
			QString msg = i18n("Unable to open the knowit file %1").arg(maindocfile.name());

			if (nogui) kdFatal()<<msg<<endl;
			else       KMessageBox::error(0, msg);

			return false;
		}

		QTextStream docstream(&maindocfile);
		docstream.setEncoding( QTextStream::UnicodeUTF8 );

		QValueList<int> ancestors;
		int lastdepth = 0;
		int lastitem = DItem::NOITEM;
		int id = 0;
		URLObject obj;

		QString line = docstream.readLine();
		while (! line.isNull())
		{
			if (line.left(9) == "\\NewEntry" || line.left(13) == "\\CurrentEntry")
			{
				QString name;
				uint depth;

				QTextIStream itemline(&line);
				itemline >> name >> depth;

				name = itemline.readLine();
				name = name.stripWhiteSpace();

				// request a new item
				createItemWithId( id ); 
				lastitem = id;
				id++;


				DDataItem *item = dataItem( lastitem );
				item->setXY( 1500+random()%1000, 1500+random()%1000);
				item->m_summary = name;

				if (depth+1 > ancestors.count())
				{
					ancestors += lastitem;
				}
				ancestors[depth] = lastitem;

				if (depth> 0)
				{
					dataItem( ancestors[depth-1] )->addChild( lastitem );
					item->setParent( ancestors[depth-1] );
				}
				else
				{
					item->setParent( DItem::NOITEM );
				}

				lastdepth++;
			}
			else if (line.left(5) == "\\Link" )
			{
				obj.m_url = line.right( line.length()- 6 );
			}
			else if (line.left(6) == "\\Descr" )
			{
				obj.m_caption = line.right( line.length()- 7 );
				DDataItem* currentitem = dataItem( lastitem );
				if (currentitem)
					currentitem->m_urllist.append( obj );
			}
			else
			{
				DDataItem* currentitem = dataItem( lastitem );

				if (currentitem)
					currentitem->m_text += line;
			}
			line = docstream.readLine();
		}
	}

	KIO::NetAccess::removeTempFile( tmpFile );

	// Notify the views attached
	QValueList<int>::iterator it;
	QValueList<int> allitems = m_map.keys();

	for ( it = allitems.begin(); it != allitems.end(); ++it )
	{
		int val = *it;
		if (m_lastid <= val) m_lastid = val+1;
		if (val < idval) continue;
		
		emit itemCreated(*it);
	}

	// make sure everything is correct (perhaps a bit paranoid, but better be safe than sorry)
	for ( it = allitems.begin(); it != allitems.end(); ++it )
	{
		DDataItem *item = dataItem(*it);
		for (unsigned int i=0; i<item->ref_count(); i++)
		{
			DRef *ref = item->ref_idx(i);
			kdDebug()<<"updating ref "<<item->Id()<<" "<<ref->m_ref<<endl;
			if (item->Id() < idval) continue;
			emit refChanged(item->Id(), ref->m_ref, true);
		}
		emit itemChanged(*it);
	}	

	if (!import) emit settingsChanged();
	return true;
}

bool DDataControl::saveToFile(const KURL & url)
{
	// check the name of the file to write to
	QString fname = url.url();
	if (! fname.endsWith(".kdi", false)) fname.append(".kdi");

	// create a tremporary file in which the data will be written
	KTempFile archfile;
	QString tmpfilename = archfile.name();

	// create also a temporary file for writing the xml document
	KTempFile docfile;

	if (archfile.status() != 0 || docfile.status() != 0)
	{
		KMessageBox::information(NULL, i18n("Cannot create a temporary file '%1' for output.").arg(tmpfilename), 
				i18n("Save Document") );
		return false;
	}

	KTar arch(tmpfilename, "application/x-gzip");
	if (! arch.open(IO_WriteOnly) )
	{
		KMessageBox::information(NULL, i18n("Cannot open file '%1' for output.").arg(tmpfilename), 
				i18n("Save Document") );
		archfile.unlink();
		docfile.unlink();
		return false;
	}

	// add the doc file
	printTree(*docfile.textStream());
	docfile.close();
	arch.addLocalFile(docfile.name(), "maindoc.xml");

	// add the cached documents
	arch.addLocalDirectory( getTmpDir()->absPath() , "." );

	// close the archive and save it
	arch.close();

	bool issaved = KIO::NetAccess::upload(tmpfilename, KURL(fname), NULL);

	emit documentChanged( !issaved );

	archfile.unlink();
	docfile.unlink();
	return issaved;
}

void DDataControl::docChanged()
{
	emit documentChanged( true );
}

void DDataControl::docNotChanged()
{
	emit documentChanged( false );
}

int DDataControl::countItems()
{
	return m_map.count();
}

bool DDataControl::canGenerate()
{
	DDataItem *rewt = dataItem( rootID() );
	if (!rewt) return false;
	if (rewt->countChildren() > 0) return true;
	return false;
}

int DDataControl::rootID()
{
	int cnt = 0, rootid = DItem::NOITEM;

	QValueList<int>::iterator it;
	QValueList<int> allitems = m_map.keys();
	QValueList<int>::iterator end = allitems.end();

	for ( it = allitems.begin(); it != end; ++it )
	{
		if ( dataItem( *it )->Parent() == DItem::NOITEM )
		{
			if ( dataItem( *it )->countFamily() > cnt )
			{
				cnt = dataItem( *it )->countFamily();
				rootid = *it;
			}
		}
	}
	return rootid;
}

void DDataControl::debugItem(int id)
{
	DDataItem *item = dataItem( id );
	if (!item)
	{
		kdWarning()<<"DDataControl::debugItem : invalid item of id : "<<id<<endl;
		return;
	}
	kdWarning()<<"***"<<endl;

	kdWarning()<<"item of id : "<<id<<endl;

	for (unsigned int i=0; i<item->countChildren(); i++)
	{
		kdWarning()<<"   - child "<<dataItem(item->childNum(i))->Id()<<endl;
	}

	kdWarning()<<"***"<<endl;
}

void DDataControl::notifyChildChange(int id)
{
	emit itemChanged(id);
}

QDir * DDataControl::getTmpDir()
{
	return m_fileholder->qDir();
}

void DDataControl::checkConsistency()
{
	QValueList<int>::iterator it;
	QValueList<int> allitems = m_map.keys();
	QValueList<int>::iterator end = allitems.end();
	for ( it = allitems.begin(); it != end; ++it )
	{
		bool changed = false;

		// check the parent
		DDataItem* item = (DDataItem*) dataItem(*it);
		if (item->Parent() != DItem::NOITEM)
		{
			if (!dataItem(item->Parent()))
			{
				item->setParent(DItem::NOITEM);
				changed = true;	
			}
		}

		// check the children
		unsigned int count = 0;
		while (count < item->countChildren())
		{
			if (!dataItem(item->childNum(count)))
			{
				item->removeChild( item->childNum(count) );
				count = 0;
				changed = true;
			}
			else
			{
				count++;
			}
		}

		// notify if something changed
		if (changed)
		{
			kdWarning()<<"inconsistency detected - item : "<<item->Id()<<endl;	
			emit itemChanged(item->Id());
		}
	}
}

void DDataControl::slotFileSpell()
{
	if (!m_spell) m_spell = new DSpell( this );
	m_spell->spell();
}

void DDataControl::setItemSelected(int id, DGuiView *view)
{
	m_itemselected = dataItem(id);
	emit itemSelected(id, view);
}

void DDataControl::notifyItemModified(int id)
{
	emit itemChanged(id);
}

void DDataControl::setProject(const KURL& url)
{
  if (!url.hasPath()) return;
  m_project = QFileInfo(url.path()).baseName();
  m_path = QFileInfo(url.path()).dirPath();
  emit settingsChanged();
}

bool DDataControl::requestRefChange(int orig, int dest, bool add, bool check)
{
	DDataItem *item = dataItem(orig);
	if (check)
	{
		if (!item)
		{
			//kdWarning()<<"item orig does not exist! "<<orig<<endl;
			return false;
		}
		if (!dataItem(dest))
		{
			//kdWarning()<<"item dest does not exist! "<<dest<<endl;
			return false;
		}
	}

	if (add)
	{
		if ( item->isRef(dest) ) return false;
		item->addRef(dest);
		addDelta( new DDeltaLink(this, orig, dest, false, true) );
	}
	else
	{
		if ( !item->isRef(dest) ) return false;
		item->rmRef(dest);
		addDelta( new DDeltaLink(this, orig, dest, true, true) );
	}
	m_reflist.append( new QPoint(orig, dest) );
	if (check) emit refChanged(orig, dest, add);
	return true;
}

void DDataControl::addDelta(DDelta* delta)
{
	emit documentChanged(true);
	if (m_undoredo_locked)
	{
		delete delta;
		return;
	}

//	kdDebug()<<"adding delta "<<delta->Type()<<endl;
	

	m_redostack.clear();
	emit redoState( false );
	
	if (! m_undostack.count()) newDelta();

	DDeltaList* lst = m_undostack.top();
	lst->insertDelta(delta);
	
	// set state to "undo is possible"
	emit undoState( true );
}

void DDataControl::newDelta()
{
	DDeltaList* lst = m_undostack.top();

	// no need to create a new delta list, the previous is empty
	if (lst) if (!lst->count()) return;
	
	// limit the undo depth
	if (m_undostack.count() > 25)
	{
		kdDebug()<<"undo stack contains more than 25 elements"<<endl;

		// eliminates elements in excess, put the other elements in a temporary stack
		QPtrStack<DDeltaList> st2; int count = 0; DDeltaList* dt;
		while ((dt = m_undostack.pop()))
		{
			if (count<=25) st2.push(dt);
			else delete dt;
		}

		// refill the undo stack
		while ((dt = st2.pop())) m_undostack.push(dt);
	}

	lst = new DDeltaList();
	m_undostack.push(lst);
}

void DDataControl::slotUndo()
{
	m_undoredo_locked = true;

	if (m_undostack.count())
	{
		DDeltaList *lst = m_undostack.pop();
		lst->applyUndo();
		m_redostack.push(lst);
	}
	
	// check and set the state to "undo is possible"
	emit undoState( m_undostack.count() );
	emit redoState( m_redostack.count() );

	m_undoredo_locked = false;
}

void DDataControl::slotRedo()
{
	m_undoredo_locked = true;
	
	if (m_redostack.count())
	{
		DDeltaList *lst = m_redostack.pop();
		lst->applyRedo();
		m_undostack.push(lst);
	}
	
	// check and set the state to "redo is possible"
	emit redoState( m_redostack.count() );
	emit undoState( m_undostack.count() );

	m_undoredo_locked = false;
}

void DDataControl::registerMapPositionChange(int id, DDataItem::Coord oldx, DDataItem::Coord oldy, DDataItem::Coord newx, DDataItem::Coord newy)
{
	if (!isRegistered(id))
	{
		kdWarning()<<"BUG : unregistered item at DDataControl::registerMapPositionChange"<<id<<endl;
		return;
	}
	addDelta( new DDeltaMove(this, id, oldx, oldy, newx, newy) );
}

void DDataControl::registerMapColorChange(int id, int oldc, int newc, QColor oldcustcol, QColor newcustcol)
{
	if (!isRegistered(id))
	{
		kdWarning()<<"BUG : unregistered item at DDataControl::registerMapColorChange"<<id<<endl;
		return;
	}
	addDelta( new DDeltaColor(this, id, oldc, newc, oldcustcol, newcustcol) );
}

void DDataControl::registerMapFlagChange(int id, int flag, bool add)
{
	if (!isRegistered(id))
	{
		kdWarning()<<"BUG : unregistered item at DDataControl::registerMapFlagChange"<<id<<endl;
		return;
	}
	addDelta( new DDeltaFlag(this, id, flag, add) );
}
#include "DDataControl.moc"
