// -*- C++ -*-

/* 
 * GChemPaint library
 * document.cc 
 *
 * Copyright (C) 2001-2007 Jean Bréfort <jean.brefort@normalesup.org>
 *
 * This program is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU General Public License as 
 * published by the Free Software Foundation; either version 2 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */

#undef G_DISABLE_DEPRECATED

#include "gchempaint-config.h"
#include "application.h"
#include "view.h"
#include "document.h"
#include "settings.h"
#include "docprop.h"
#include "reaction.h"
#include "mesomery.h"
#include "text.h"
#include "theme.h"
#include "window.h"
#include <goffice/utils/go-rangefunc.h>
#include <unistd.h>
#include <libgen.h>
#include <gtk/gtk.h>
#include <libgnomevfs/gnome-vfs-file-info.h>
#include <libgnomevfs/gnome-vfs-ops.h>
#include <glib/gi18n-lib.h>
#include <openbabel/mol.h>
#include <openbabel/obconversion.h>
#include <clocale>
#include <stack>
#include <cstring>

using namespace OpenBabel;
using namespace std;

SignalId OnChangedSignal;
SignalId OnDeleteSignal;
SignalId OnThemeChangedSignal;

gcpDocument::gcpDocument(gcpApplication *App, bool StandAlone, gcpWindow *window):
	Document (App),
	m_FileType ("application/x-gchempaint"),
	m_OpID (0),
	m_LastStackSize (0)
{
	m_pApp = App;
	m_pView = NULL;
	m_Window = window;
	m_filename = NULL;
	m_label = NULL;
	m_title = NULL;
	m_bWriteable = true;
	m_PangoAttrList = pango_attr_list_new ();
	m_Theme = NULL;
	SetTheme (ThemeManager.GetTheme ("Default"));
	m_pView = new gcpView(this, !StandAlone);
	m_bIsLoading = m_bUndoRedo = false;
	g_date_set_time(&CreationDate, time(NULL));
	g_date_clear(&RevisionDate, 1);
	const char* chn = getenv ("REAL_NAME");
	if (!chn)
		chn = getenv ("USERNAME");
	if (chn)
		m_author = g_strdup(chn);
	else
		m_author = NULL;
	chn = getenv ("E_MAIL");
	if (!chn)
		chn = getenv ("EMAIL_ADDRESS");
	if (chn)
		m_mail = g_strdup (chn);
	else
		m_mail = NULL;
	m_comment = NULL;
	m_pCurOp = NULL;
	m_bReadOnly = false;
	SetActive ();
	if (window && App)
		App->AddWindow (window);
}

gcpDocument::~gcpDocument ()
{
	m_bIsLoading = true;
	if (m_pCurOp)
		delete m_pCurOp;
	m_pCurOp = NULL;
	if (m_filename)
		g_free (m_filename);
	if (m_title)
		g_free (m_title);
	if (m_label)
		g_free (m_label);
	if (m_author)
		g_free (m_author);
	if (m_mail)
		g_free (m_mail);
	if (m_comment)
		g_free (m_comment);
	map<string, Object *>::iterator it;
	Object *obj;
	while (HasChildren ()) {
		obj = GetFirstChild (it);
		obj->Lock ();
		Remove (obj);
	}
	if (m_pView)
		delete m_pView;
	pango_attr_list_unref (m_PangoAttrList);
	if (m_Theme)
		m_Theme->RemoveClient (this);
	if (m_App)
		static_cast<gcpApplication*> (m_App)->SetActiveDocument (NULL);
	while (!m_RedoList.empty ()) {
		delete m_RedoList.front ();
		m_RedoList.pop_front ();
	}
	while (!m_UndoList.empty ()) {
		delete m_UndoList.front ();
		m_UndoList.pop_front ();
	}
}

GtkWidget* gcpDocument::GetWidget()
{
	return (m_pView) ? m_pView->GetWidget() : NULL;
}


const gchar* gcpDocument::GetTitle()
{
	if (m_title) return m_title;
	else return GetLabel();
}

const gchar* gcpDocument::GetLabel()
{
	return m_label;
}

void gcpDocument::SetFileName (string const &Name, const gchar* mime_type)
{
	if (m_filename)
		g_free (m_filename);
	m_filename = g_strdup (Name.c_str ());
	m_FileType = mime_type;
	char *path = g_path_get_dirname (m_filename);
	m_pApp->SetCurDir (path);
	g_free (path);
	int i = strlen (m_filename) - 1;
	while ((m_filename[i] != '/') && (i >= 0))
		i--;
	i++;
	int j = strlen (m_filename) - 1;
	while ((i < j) && (m_filename[j] != '.'))
		j--;
	if (m_label)
		g_free (m_label);
	char *ext = m_filename + j + 1;
	list<string> &exts = m_pApp->GetExtensions (m_FileType);
	list<string>::iterator cur, end = exts.end ();
	for (cur = exts.begin (); cur != end; cur++)
		if (*cur == ext) {
			m_label = g_strndup (m_filename + i, j - i);
			break;
		}
	if (!m_label)
		m_label = g_strdup (m_filename + i);
}

void gcpDocument::BuildBondList(list<gcpBond*>& BondList, Object* obj)
{
	Object* pObject;
	map<string, Object*>::iterator i;
	for (pObject = obj->GetFirstChild(i); pObject; pObject = obj->GetNextChild(i))
		if (pObject->GetType() == BondType) BondList.push_back((gcpBond*)(*i).second);
		else BuildBondList(BondList, pObject);
}


bool gcpDocument::ImportOB(OBMol& Mol)
{
	//Title, dates, author and so on are not imported and so are invalid
	if (m_title) {g_free(m_title) ; m_title = NULL;}
	if (m_author) {g_free(m_author) ; m_author = NULL;}
	if (m_mail) {g_free(m_mail) ; m_mail = NULL;}
	if (m_comment) {g_free(m_comment) ; m_comment = NULL;}
	g_date_clear(&CreationDate, 1);
	g_date_clear(&RevisionDate, 1);
	m_title = g_strdup(Mol.GetTitle()); // Hmm, and if there are several molecules?
	OBAtom *atom;
	gcpAtom* pAtom;
	vector<OBNodeBase*>::iterator i;
	for (atom = Mol.BeginAtom(i); atom; atom = Mol.NextAtom(i))
	{
		if (atom->GetAtomicNum () == 0)
			continue;
		AddAtom(pAtom = new gcpAtom(atom));
	}
	gcpAtom *begin, *end;
	gcpBond *pBond;
	unsigned char order;
	gchar* Id;
	OBBond *bond;
	vector<OBEdgeBase*>::iterator j;
	for (bond = Mol.BeginBond (j); bond; bond = Mol.NextBond (j)) {
		Id = g_strdup_printf ("a%d", bond->GetBeginAtomIdx ());
		begin = (gcpAtom*) GetDescendant (Id);//Don't verify it is really an atom?
		g_free (Id);
		Id = g_strdup_printf ("a%d", bond->GetEndAtomIdx ());
		end = (gcpAtom*) GetDescendant (Id);//Don't verify it is really an atom?
		g_free (Id);
		if (!end)
			continue;
		order = (unsigned char) (bond->GetBO ());
		if ((pBond = (gcpBond*) begin->GetBond (end)) != NULL) {
			pBond->IncOrder (order);
			m_pView->Update (pBond);
			m_pView->Update (begin);
			m_pView->Update (end);
		} else {
			Id = g_strdup_printf ("b%d", bond->GetIdx());
			pBond = new gcpBond (begin, end, order);
			if (bond->IsWedge ())
				pBond->SetType (UpBondType);
			else if (bond->IsHash ())
				pBond->SetType (DownBondType);
			pBond->SetId (Id);
			g_free (Id);
			AddBond (pBond);
		}
	}
	m_Empty = !HasChildren ();
 	if (m_Window)
		m_Window->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", HasChildren ());
	return true;
}

void gcpDocument::BuildAtomTable(map<string, unsigned>& AtomTable, Object* obj, unsigned& index)
{
	Object* pObject;
	map<string, Object*>::iterator i;
	for (pObject = obj->GetFirstChild(i); pObject; pObject = obj->GetNextChild(i))
		if (pObject->GetType() == AtomType) AtomTable[(*i).second->GetId()] = index++;
		else BuildAtomTable(AtomTable, pObject, index);
}

void gcpDocument::ExportOB()
{
	OBMol Mol;
	map<string, unsigned>::iterator i;
	map<string, unsigned> AtomTable;
	list<gcpBond*> BondList;
	OBAtom Atom;
	gcpAtom* pgAtom;
	unsigned index = 1;
	double x, y, z;
	gchar *old_num_locale;
	map< string, Object * >::iterator m;
	stack<map< string, Object * >::iterator> iters;
	set<Object *> Mols;
	Object *Cur = this, *Ob;
	try {
		ostringstream ofs;
		GnomeVFSHandle *handle = NULL;
		GnomeVFSFileSize n;
		GnomeVFSResult res;
		res = gnome_vfs_open (&handle, m_filename, GNOME_VFS_OPEN_WRITE);
		if (res == GNOME_VFS_ERROR_NOT_FOUND)
			res = gnome_vfs_create (&handle, m_filename, GNOME_VFS_OPEN_WRITE, true, 0666);
		if (res != GNOME_VFS_OK)
			throw (int) res;
		old_num_locale = g_strdup(setlocale(LC_NUMERIC, NULL));
		setlocale(LC_NUMERIC, "C");
		OBConversion Conv;
		OBFormat* pOutFormat = Conv.FormatFromMIME (m_FileType.c_str ());
		if (pOutFormat != NULL) {
			Conv.SetOutFormat (pOutFormat);
			Ob = GetFirstChild (m);
			while (Ob) {
				if (Ob->GetType () == MoleculeType)
					Mols.insert (Ob);
				else if (Ob->HasChildren ()) {
					Cur = Ob;
					iters.push (m);
					Ob = Cur->GetFirstChild (m);
					continue;
				}
				Ob = Cur->GetNextChild (m);
				while (!Ob && !iters.empty ()) {
					m = iters.top ();
					iters.pop ();
					Cur = Cur->GetParent ();
					Ob = Cur->GetNextChild (m);
				}
			}
			set<Object *>::iterator mi, mend = Mols.end ();
			unsigned nb = 1;
			Conv.SetOneObjectOnly (false);
			for (mi = Mols.begin (); mi != mend; mi++)
			{
				Ob = *mi;
				if (nb == Mols.size ())
					Conv.SetOneObjectOnly (true);
				Mol.BeginModify ();
				index = 1;
				BuildAtomTable (AtomTable, Ob, index);
				Mol.ReserveAtoms (AtomTable.size ());
				Mol.SetTitle ((char*) GetTitle ());
				Mol.SetDimension (2);
				for (i = AtomTable.begin (); i != AtomTable.end (); i++) {
					pgAtom = (gcpAtom*) Ob->GetDescendant ((*i).first.data ());
					Atom.SetIdx ((*i).second);
					Atom.SetAtomicNum (pgAtom->GetZ ());
					pgAtom->GetCoords (&x, &y, &z);
					Atom.SetVector (x / 100., 4. - y / 100., z / 100.);
					Atom.SetFormalCharge (pgAtom->GetCharge ());
					Mol.AddAtom (Atom);
					Atom.Clear ();
				}
				BuildBondList (BondList, Ob);
				list<gcpBond*>::iterator j;
				int start, end, order, flag;
				for (j = BondList.begin (); j != BondList.end (); j++)
				{
					order = (*j)->GetOrder ();
					start = AtomTable[(*j)->GetAtom (0)->GetId ()];
					end = AtomTable[(*j)->GetAtom (1)->GetId ()];
					switch ((*j)->GetType ()) {
					case UpBondType:
						flag = OB_WEDGE_BOND;
						break;
					case DownBondType:
						flag = OB_HASH_BOND;
						break;
					default:
						flag = 0;
					}
					Mol.AddBond (start, end, order, flag);
				}
				Mol.EndModify ();
				Conv.SetOutputIndex (nb++);
				Conv.Write (&Mol, &ofs);
				Mol.Clear ();
				AtomTable.clear ();
				BondList.clear ();
			}
		}
		setlocale(LC_NUMERIC, old_num_locale);
		g_free(old_num_locale);
		if ((res = gnome_vfs_write (handle, ofs.str ().c_str (), (GnomeVFSFileSize) ofs.str ().size (), &n)) != GNOME_VFS_OK)
			throw (int) res;
		gnome_vfs_close (handle);
		SetReadOnly (false);
	}
	catch (int n) {
		fprintf(stderr, "gnome-vfs error #%d\n",n);
	}
}

void gcpDocument::Print(GnomePrintContext *pc, gdouble width, gdouble height)
{
	m_pView->Print(pc, width, height);
}

void gcpDocument::AddAtom(gcpAtom* pAtom)
{
	int i = 1;
	char id[8];
	const gchar *Id;
	Id = pAtom->GetId();
	if (Id == NULL) 
	{
		id[0] = 'a';
		do
			snprintf(id+1, 7, "%d", i++);
		while (GetDescendant(id) != NULL);
		pAtom->SetId(id);
		Id = id;
	}
	m_pView->AddObject(pAtom);
	if (m_bIsLoading)
		return;
	gcpMolecule* mol = new gcpMolecule();
	i = 1;
	id[0] = 'm';
	do
		snprintf(id+1, 7, "%d", i++);
	while (GetDescendant(id) != NULL);
	mol->SetId(id);
	AddChild(mol);
	mol->AddAtom(pAtom);
}

void gcpDocument::AddFragment(gcpFragment* pFragment)
{
	int i = 1;
	char id[8];
	const gchar *Id;
	Id = pFragment->GetId();
	if (Id == NULL) 
	{
		id[0] = 'f';
		do
			snprintf(id+1, 7, "%d", i++);
		while (GetDescendant(id) != NULL);
		pFragment->SetId(id);
		Id = id;
	}
	m_pView->AddObject(pFragment);
	if (m_bIsLoading)
		return;
//	AddObject(pFragment);
	if (!pFragment->GetMolecule())
	{
		gcpMolecule* mol = new gcpMolecule();
		i = 1;
		id[0] = 'm';
		do
			snprintf(id+1, 7, "%d", i++);
		while (GetDescendant(id) != NULL);
		mol->SetId(id);
		AddChild(mol);
		mol->AddFragment(pFragment);
	}
	pFragment->AnalContent();
}

void gcpDocument::AddBond(gcpBond* pBond)
{
	int i = 1;
	char id[8];
	const gchar *Id;
	Id = pBond->GetId();
	if (Id == NULL)
	{
		id[0] = 'b';
		do
			snprintf(id+1, 7, "%d", i++);
		while (GetDescendant(id) != NULL);
		pBond->SetId(id);
	}
	AddChild (pBond);
	gcpAtom *pAtom0 = (gcpAtom*)pBond->GetAtom(0), *pAtom1 = (gcpAtom*)pBond->GetAtom(1);
	m_pView->Update(pAtom0);
	m_pView->Update(pAtom1);
	m_pView->AddObject(pBond);
	if (m_bIsLoading)
		return;
	//search molecules
	gcpMolecule * pMol0 = (gcpMolecule*)pAtom0->GetMolecule(),  *pMol1 = (gcpMolecule*)pAtom1->GetMolecule();
	if (pMol0 && pMol1)
	{
		if (pMol0 == pMol1) //new cycle
		{
			pMol0->UpdateCycles(pBond);
			m_pView->Update(pBond);
		}
		else //merge two molecules
		{
			pMol0->Merge(pMol1);
		}
		pMol0->AddBond(pBond);
	}
	else if (pMol0 || pMol1) //add bond and atom to existing molecule
	{
		if (!pMol0) pMol0 = pMol1;
		pMol0->AddAtom(pAtom0);
		pMol0->AddBond(pBond);
	}
	else //new molecule
	{
		i = 1;
		id[0] = 'm';
		do
			snprintf(id+1, 7, "%d", i++);
		while (GetDescendant(id) != NULL);
		pMol0 = new gcpMolecule(pAtom0);
		pMol0->SetId(id);
		AddChild(pMol0);
	}
}

static int cb_xml_to_vfs (GnomeVFSHandle *handle, const char* buf, int nb)
{
	GnomeVFSFileSize ndone;
	return (int) gnome_vfs_write (handle, buf, nb, &ndone);
}

void gcpDocument::Save()
{
	if (!m_filename || !m_bWriteable || m_bReadOnly)
		return;
	xmlDocPtr xml = NULL;
	char *old_num_locale, *old_time_locale;
	
	old_num_locale = g_strdup (setlocale (LC_NUMERIC, NULL));
	setlocale(LC_NUMERIC, "C");
	old_time_locale = g_strdup (setlocale (LC_TIME, NULL));
	setlocale (LC_TIME, "C");

	try {
		if (m_FileType != "application/x-gchempaint")
			ExportOB ();
		else {
			xml = BuildXMLTree();
			xmlSetDocCompressMode (xml, CompressionLevel);
			
			xmlIndentTreeOutput = true;
			xmlKeepBlanksDefault(0);
		
			GnomeVFSFileInfo *info = gnome_vfs_file_info_new ();
			gnome_vfs_get_file_info (m_filename, info, GNOME_VFS_FILE_INFO_DEFAULT);

			if (GNOME_VFS_FILE_INFO_LOCAL (info)) {
				gnome_vfs_file_info_unref (info);
				if (xmlSaveFormatFile (m_filename, xml, true) < 0) /*Error(SAVE)*/;
			} else {
				gnome_vfs_file_info_unref (info);
				xmlOutputBufferPtr buf = xmlAllocOutputBuffer (NULL);
				GnomeVFSHandle *handle;
				GnomeVFSResult result = gnome_vfs_open (&handle, m_filename, GNOME_VFS_OPEN_WRITE);
				if (result == GNOME_VFS_ERROR_NOT_FOUND)
					result = gnome_vfs_create (&handle, m_filename, GNOME_VFS_OPEN_WRITE, true, 0666);
				if (result != GNOME_VFS_OK)
					throw 1;
				buf->context = handle;
				buf->closecallback = (xmlOutputCloseCallback) gnome_vfs_close;
				buf->writecallback = (xmlOutputWriteCallback) cb_xml_to_vfs;
				int n = xmlSaveFormatFileTo (buf, xml, NULL, true);
				if (n < 0)
					throw 1;
				SetReadOnly (false);
			}
		}
		SetDirty (false);
		m_LastStackSize = m_UndoList.size ();
		m_OpID = m_UndoList.front ()->GetID ();
	}
	catch (int num) {
		if (xml)
			xmlFreeDoc (xml);
		xml = NULL;
//		Error (SAVE);
	}
	setlocale (LC_NUMERIC, old_num_locale);
	g_free (old_num_locale);
	setlocale (LC_TIME, old_time_locale);
	g_free (old_time_locale);
}

bool gcpDocument::Load (xmlNodePtr root)
{
	if (m_title) {
		g_free(m_title);
		m_title = NULL;
	}
	if (m_author) {
		g_free(m_author);
		m_author = NULL;
	}
	if (m_mail) {
		g_free(m_mail);
		m_mail = NULL;
	}
	if (m_comment) {
		g_free(m_comment);
		m_comment = NULL;
	}
	g_date_clear (&CreationDate, 1);
	g_date_clear (&RevisionDate, 1);
	xmlNodePtr node, child;
	char* tmp;
	Object* pObject;
	tmp = (char*) xmlGetProp (root, (xmlChar*) "id");
	if (tmp) {
		SetId (tmp);
		xmlFree (tmp);
	}
	tmp = (char*) xmlGetProp (root, (xmlChar*) "creation");
	if (tmp) {
		g_date_set_parse(&CreationDate, tmp);
		if (!g_date_valid(&CreationDate)) g_date_clear(&CreationDate, 1);
		xmlFree (tmp);
	}
	tmp = (char*) xmlGetProp (root, (xmlChar*) "revision");
	if (tmp) {
		g_date_set_parse(&RevisionDate, tmp);
		if (!g_date_valid(&RevisionDate)) g_date_clear(&RevisionDate, 1);
		xmlFree (tmp);
	}

	node = GetNodeByName (root, (char*) "title");
	if (node)
	{
		tmp = (char*) xmlNodeGetContent (node);
		if (tmp) {
			m_title = g_strdup(tmp);
			xmlFree (tmp);
		}
	}
	if (m_Window)
		m_Window->SetTitle (GetTitle ());
	node = GetNodeByName (root, (char*) "author");
	if (node)
	{
		tmp = (char*) xmlGetProp (node, (xmlChar*) "name");
		if (tmp) {
			m_author = g_strdup(tmp);
			xmlFree (tmp);
		}
		tmp = (char*) xmlGetProp (node, (xmlChar*) "e-mail");
		if (tmp) {
			m_mail = g_strdup(tmp);
			xmlFree (tmp);
		}
	}
	node = GetNodeByName (root, (char*) "comment");
	if (node)
	{
		tmp = (char*) xmlNodeGetContent (node);
		if (tmp) {
			m_comment = g_strdup(tmp);
			xmlFree (tmp);
		}
	}

	node = GetNodeByName (root, (char*) "theme");
	if (node) {
		gcpTheme *pTheme = new gcpTheme (NULL), *pLocalTheme;
		pTheme->Load (node);
		pLocalTheme = ThemeManager.GetTheme (_(pTheme->GetName ().c_str ()));
		if (!pLocalTheme)
			pLocalTheme = ThemeManager.GetTheme (pTheme->GetName ().c_str ());
		if (pLocalTheme && *pLocalTheme == *pTheme) {
			SetTheme (pLocalTheme);
			delete pTheme;
		} else {
			ThemeManager.AddFileTheme (pTheme, GetTitle ());
			SetTheme (pTheme);
		}
	}

	m_bIsLoading = true;
	node = root->children;
	while (node) {
		child = (strcmp ((const char*) node->name, "object"))? node: node->children;
		pObject = CreateObject ((const char*) child->name, this);
		if (pObject) {
			if (!pObject->Load (child))
				delete pObject;
			else
				m_pView->AddObject (pObject);
		}
		node = node->next;
	}
	m_pView->Update (this);
	m_Empty = !HasChildren ();
	Update ();
	m_bIsLoading = false;
	if (m_Window)
		m_Window->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", HasChildren ());
	m_pView->EnsureSize ();
	return true;
}
	
void gcpDocument::ParseXMLTree (xmlDocPtr xml)
{
	Load (xml->children);
}

xmlDocPtr gcpDocument::BuildXMLTree()
{
	xmlDocPtr xml;
	xmlNodePtr node;
	xmlNsPtr ns;

	xml = xmlNewDoc((xmlChar*)"1.0");
//FIXME: do something if an error occurs 
	if (xml == NULL)
		throw (int) 0;
	
	xmlDocSetRootElement (xml,  xmlNewDocNode (xml, NULL, (xmlChar*) "chemistry", NULL));
	ns = xmlNewNs (xml->children, (xmlChar*) "http://www.nongnu.org/gchempaint", (xmlChar*) "gcp");
	xmlSetNs (xml->children, ns);
	if (!g_date_valid (&CreationDate))
		g_date_set_time (&CreationDate, time (NULL));
	g_date_set_time (&RevisionDate, time (NULL));
	gchar buf[64];
	g_date_strftime (buf, sizeof (buf), "%m/%d/%Y", &CreationDate);
	xmlNewProp (xml->children, (xmlChar*) "creation", (xmlChar*) buf);
	g_date_strftime (buf, sizeof (buf), "%m/%d/%Y", &RevisionDate);
	xmlNewProp (xml->children, (xmlChar*) "revision", (xmlChar*) buf);
	node = xmlNewDocNode(xml, NULL, (xmlChar*)"generator", (xmlChar*)"GChemPaint "VERSION);
	if (node)
		xmlAddChild (xml->children, node);
	else
		throw (int) 0;

	if (m_title && *m_title) {
		node = xmlNewDocNode (xml, NULL, (xmlChar*) "title", (xmlChar*) m_title);
		if (node)
			xmlAddChild (xml->children, node);
		else
			throw (int) 0;
	}
	if ((m_author && *m_author) || (m_mail && *m_mail)) {
		node = xmlNewDocNode (xml, NULL, (xmlChar*) "author", NULL);
		if (node) {
			if (m_author && *m_author)
				xmlNewProp (node, (xmlChar*) "name", (xmlChar*) m_author);
			if (m_mail && *m_mail)
				xmlNewProp (node, (xmlChar*) "e-mail", (xmlChar*) m_mail);
			xmlAddChild (xml->children, node);
		}
		else
			throw (int) 0;
	}
	if (m_comment && *m_comment) {
		node = xmlNewDocNode (xml, NULL, (xmlChar*) "comment", (xmlChar*) m_comment);
		if (node)
			xmlAddChild (xml->children, node);
		else
			throw (int) 0;
	}

	if (!m_Theme->Save (xml))
		throw (int) 0;
	if (!SaveChildren (xml, xml->children))
		throw 1;

	return xml;
}

void gcpDocument::Update()
{
	std::set<Object*>::iterator i,  end = m_DirtyObjects.end ();
	TypeId Id;
	for (i = m_DirtyObjects.begin(); i != end; i++) {
		Id = (*i)->GetType ();
		switch (Id) {
		case BondType:
			m_pView->Update ((gcpBond*)(*i));
			break;
		}
	}
	m_DirtyObjects.clear ();
}
	
void gcpDocument::RemoveAtom(gcpAtom* pAtom)
{
	std::map<Atom*, Bond*>::iterator i;
	gcpBond* pBond;
	while ((pBond = (gcpBond*)pAtom->GetFirstBond(i)))
	{
		if (!m_bUndoRedo) m_pCurOp->AddObject(pBond);
		RemoveBond(pBond);
	}
	gcpMolecule *pMol = (gcpMolecule*)pAtom->GetMolecule();
	delete pMol;
	m_pView->Remove(pAtom);
	delete pAtom;
}

void gcpDocument::RemoveFragment(gcpFragment* pFragment)
{
	std::map<Atom*, Bond*>::iterator i;
	gcpAtom* pAtom = pFragment->GetAtom();
	gcpBond* pBond;
	while ((pBond = (gcpBond*)pAtom->GetFirstBond(i)))
	{
		if (!m_bUndoRedo) m_pCurOp->AddObject(pBond);
		RemoveBond(pBond);
	}
	gcpMolecule *pMol = (gcpMolecule*)pFragment->GetMolecule();
	delete pMol;
	m_pView->Remove(pFragment);
	delete pFragment;
}

void gcpDocument::RemoveBond (gcpBond* pBond)
{
	m_pView->Remove (pBond);
	gcpAtom *pAtom0, *pAtom1;
	pAtom0 = (gcpAtom*) pBond->GetAtom (0);
	pAtom1 = (gcpAtom*) pBond->GetAtom (1);
	gcpMolecule *pMol = (gcpMolecule*) pBond->GetMolecule ();
	char id[16];
	pMol->Lock (true);
	pAtom0->RemoveBond (pBond);
	m_pView->Update (pAtom0);
	pAtom1->RemoveBond (pBond);
	m_pView->Update (pAtom1);
	pMol->Lock (false);
	if (pBond->IsCyclic ()) {
		pMol->Remove (pBond);
		pMol->UpdateCycles ();
		Update ();
	} else {
		Object *Parent = pMol->GetParent ();
		Parent->Lock ();
		int i0 = 1;
		string align_id = pMol->GetAlignmentId ();
		delete pMol;
		gcpMolecule * pMol = new gcpMolecule ();
		pMol->Lock (true);
		do
			snprintf (id, sizeof (id), "m%d", i0++);
		while (GetDescendant (id) != NULL);
		pMol->SetId (id);
		Parent->AddChild (pMol); /* do not update at this point it's the caller responsibility */
		Object* pObject = pAtom0->GetParent ();
		if (pObject->GetType () == FragmentType)
			pMol->AddFragment ((gcpFragment*) pObject);
		else
			pMol->AddAtom (pAtom0);
		pMol->UpdateCycles ();
		if (align_id.size ()) {
			Object *obj = pMol->GetDescendant (align_id.c_str ());
			if (obj)
				pMol->SelectAlignmentItem (obj);
			align_id = "";
		}
		pMol->Lock (false);
		do
			snprintf (id, sizeof (id), "m%d", i0++);
		while (GetDescendant (id) != NULL);
		pMol = new gcpMolecule ();
		pMol->Lock (true);
		pMol->SetId (id);
		Parent->AddChild (pMol); /* do not update at this point it's the caller responsibility */
		pObject = pAtom1->GetParent ();
		if (pObject->GetType () == FragmentType)
			pMol->AddFragment ((gcpFragment*) pObject);
		else
			pMol->AddAtom (pAtom1);
		pMol->UpdateCycles ();
		if (align_id.size ()) {
			Object *obj = pMol->GetDescendant (align_id.c_str ());
			if (obj)
				pMol->SelectAlignmentItem (obj);
		}
		pMol->Lock (false);
		if ((pAtom0->GetZ () == 6) && (pAtom0->GetBondsNumber () == 0))
			m_pView->Update (pAtom0);
		if ((pAtom1->GetZ () == 6) && (pAtom1->GetBondsNumber () == 0))
			m_pView->Update (pAtom1);
		Parent->Lock (false);
	}
	m_DirtyObjects.erase (pBond);
	delete pBond;
}
	
void gcpDocument::Remove(Object* pObject)
{
	switch(pObject->GetType())
	{
		case AtomType:
			RemoveAtom((gcpAtom*)pObject);
			break;
		case FragmentType:
			RemoveFragment((gcpFragment*)pObject);
			break;
		case BondType:
			RemoveBond((gcpBond*)pObject);
			break;
		case MoleculeType: {
				((gcpMolecule*)pObject)->Clear();
				m_pView->Remove(pObject);
				map<string, Object*>::iterator i;
				Object* object = pObject->GetFirstChild(i);
				while (object)
				{
					m_pView->Remove(object);
					delete object;
					object = pObject->GetNextChild(i);
				}
				delete pObject;
			}
			break;
		default: {
				m_pView->Remove(pObject);
				map<string, Object*>::iterator i;
				Object* object = pObject->GetFirstChild(i);
				while (object)
				{
					if (pObject->IsLocked ())
						object->Lock ();
					Remove(object);
					object = pObject->GetFirstChild(i);
				}
				delete pObject;
			}
			break;
	}
}
	
void gcpDocument::Remove(const char* Id)
{
	Object* pObj = GetDescendant(Id);
	if (pObj) {
		pObj->Lock ();
		Remove(pObj);
	}
}

void gcpDocument::OnProperties()
{
	new gcpDocPropDlg(this);
}

void gcpDocument::SetTitle (const gchar* title)
{
	g_free (m_title);
	m_title = (title && *title)? g_strdup (title): NULL;
}

void gcpDocument::SetAuthor (const gchar* author)
{
	g_free (m_author);
	m_author = (author && *author)? g_strdup (author): NULL;
}

void gcpDocument::SetMail (const gchar* mail)
{
	g_free (m_mail);
	m_mail = (mail && *mail)? g_strdup (mail): NULL;
}

void gcpDocument::SetComment (const gchar* comment)
{
	g_free (m_comment);
	m_comment = (comment && *comment)? g_strdup (comment): NULL;
}
	
void gcpDocument::AddObject(Object* pObject)
{
	if (!pObject->GetParent()) AddChild(pObject);
	m_pView->AddObject(pObject);
	if (m_bIsLoading || m_bUndoRedo) return;
	if (!m_pCurOp) {
		m_pCurOp = new gcpAddOperation(this, ++m_OpID);
		m_pCurOp->AddObject(pObject);
	}
}

void gcpDocument::OnUndo()
{
	if (m_pApp->GetActiveTool()->OnUndo()) return;
	m_bUndoRedo = true;
	if (!m_UndoList.empty())
	{
		gcpOperation* Op = m_UndoList.front();
		Op->Undo();
		m_UndoList.pop_front();
		m_RedoList.push_front(Op);
		m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Redo", true);
	}
	if (m_UndoList.empty())
		m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Undo", false);
	m_Window->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", HasChildren ());
	m_bUndoRedo = false;
	Update ();
	EmptyTranslationTable ();
	SetDirty (m_LastStackSize != m_UndoList.size () || (m_LastStackSize > 0 && m_OpID != m_UndoList.front ()->GetID ()));
	m_Empty = !HasChildren ();
}

void gcpDocument::OnRedo()
{
	if (m_pApp->GetActiveTool()->OnRedo()) return;
	m_bUndoRedo = true;
	if (!m_RedoList.empty())
	{
		gcpOperation* Op = m_RedoList.front();
		Op->Redo();
		m_RedoList.pop_front();
		m_UndoList.push_front(Op);
		m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Undo", true);
	}
	if (m_RedoList.empty())
		m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Redo", false);
	m_Window->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", HasChildren ());
	m_bUndoRedo = false;
	EmptyTranslationTable ();
	SetDirty (m_LastStackSize != m_UndoList.size () || (m_LastStackSize > 0 && m_OpID != m_UndoList.front ()->GetID ()));
	m_Empty = !HasChildren ();
}

void gcpDocument::FinishOperation()
{
	if (!m_pCurOp) return;//FIXME: should at least emit a warning
	m_UndoList.push_front(m_pCurOp);
	while (!m_RedoList.empty())
	{
		delete m_RedoList.front();
		m_RedoList.pop_front();
	}
	m_pCurOp = NULL;
	SetDirty (true);
	m_Empty = !HasChildren ();
	if (m_Window) {
		m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Undo", true);
		m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Redo", false);
		m_Window->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", HasChildren ());
	}
	Update ();
}

void gcpDocument::AbortOperation()
{
	if (m_pCurOp) delete m_pCurOp;
	m_pCurOp = NULL;
} 

void gcpDocument::PopOperation()
{
	if (!m_UndoList.empty())
	{
		delete m_UndoList.front();
		m_UndoList.pop_front();
		if (m_UndoList.empty() && m_Window)
			m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Undo", false);
	}
	SetDirty (m_LastStackSize != m_UndoList.size () || (m_LastStackSize > 0 && m_OpID != m_UndoList.front ()->GetID ()));
}

void gcpDocument::PushOperation(gcpOperation* operation, bool undo)
{
	if (!m_pCurOp || (operation != m_pCurOp))
	{
		cerr << "Warning: Incorrect operation" << endl;
		return;
	}
	if (undo) FinishOperation();
	else
	{
		while (!m_RedoList.empty())
		{
			delete m_RedoList.front();
			m_RedoList.pop_front();
		}
		m_RedoList.push_front(operation);
		m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Redo", true);
	}
	m_pCurOp = NULL;
}

void gcpDocument::SetActive ()
{
	if (m_Window) {
		m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Undo", !m_UndoList.empty ());
		m_Window->ActivateActionWidget ("/MainMenu/EditMenu/Redo", !m_RedoList.empty ());
		m_Window->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", HasChildren ());
		m_Window->ActivateActionWidget ("/MainMenu/FileMenu/Save", !m_bReadOnly);
		m_Window->ActivateActionWidget ("/MainToolbar/Save", !m_bReadOnly);
	}
}

extern xmlDocPtr pXmlDoc;

void gcpDocument::LoadObjects(xmlNodePtr node)
{
	xmlNodePtr child = node->children, child1;
	string str;
	while (child)	//Add everything except bonds
	{
		if (!strcmp((const char*)child->name, "atom"))
		{
			gcpAtom* pAtom = new gcpAtom();
			AddChild (pAtom);
			pAtom->Load(child);
			AddAtom(pAtom);
		}
		else if (!strcmp((const char*)child->name, "fragment"))
		{
			gcpFragment* pFragment = new gcpFragment();
			AddChild(pFragment);
			pFragment->Load(child);
			AddFragment(pFragment);
		}
		else if (!strcmp((const char*)child->name, "bond"));
		else
		{
			m_bIsLoading = true;
			child1 = (strcmp((const char*)child->name, "object"))? child: child->children;
			str = (const char*)child1->name;
			Object* pObject = Object::CreateObject(str, this);
			pObject->Load(child1);
			AddObject(pObject);
			m_pView->Update(pObject);//FIXME: should not be necessary, but solve problem with cyclic double bonds
			m_bIsLoading = false;
		}
		child = child->next;
	}
	//Now, add bonds
	child = GetNodeByName (node, (char*) "bond");
	while (child)
	{
		gcpBond* pBond = new gcpBond();
		AddChild(pBond);
		if (pBond->Load(child))
			AddBond(pBond);
		else delete pBond;
		child = GetNextNodeByName (child->next, (char*) "bond");
	}
}

gcpOperation* gcpDocument::GetNewOperation(gcpOperationType type)
{
	m_OpID++;
	switch (type)
	{
		case GCP_ADD_OPERATION:
			return m_pCurOp = new gcpAddOperation(this, m_OpID);
		case GCP_DELETE_OPERATION:
			return m_pCurOp = new gcpDeleteOperation(this, m_OpID);
		case GCP_MODIFY_OPERATION:
			return m_pCurOp = new gcpModifyOperation(this, m_OpID);
		default:
			return NULL;
	}
}

void gcpDocument::AddData(xmlNodePtr node)
{
	xmlNodePtr child;
	string str;
	Object* pObject;
	m_bIsLoading = true;
	EmptyTranslationTable();
	GtkWidget* w = m_pView->GetWidget();
	gcpWidgetData* pData = (gcpWidgetData*)g_object_get_data(G_OBJECT(w), "data");
	while (node)
	{
		child = (strcmp((const char*)node->name, "object"))? node: node->children;
		str = (const char*)child->name;
		pObject = Object::CreateObject(str, this);
		AddObject(pObject);
		if (!pObject->Load(child)) Remove (pObject);
		else
		{
			m_pView->Update(pObject);//FIXME: should not be necessary, but solve problem with cyclic double bonds
			pData->SetSelected(pObject);
		}
		node = node->next;
	}
	m_bIsLoading = false;
	EmptyTranslationTable();
	FinishOperation();
}

void gcpDocument::ExportImage (string const &filename, const char* type, int resolution)
{
	m_pView->ExportImage (filename, type, resolution);
}

void gcpDocument::SetReadOnly (bool ro)
{
	m_bReadOnly = ro;
	if (!ro && (m_FileType != "application/x-gchempaint")) {
		OBFormat *f = OBConversion::FormatFromMIME (m_FileType.c_str ());
		m_bReadOnly = (f == NULL)? true: f->Flags () & NOTWRITABLE;
	}
	m_bUndoRedo = true;
	if (m_Window) {
		m_Window->ActivateActionWidget ("/MainMenu/FileMenu/Save", !m_bReadOnly);
		m_Window->ActivateActionWidget ("/MainToolbar/Save", !m_bReadOnly);
	}
}

double gcpDocument::GetYAlign ()
{
	if (GetChildrenNumber () == 1) {
		map<string, Object*>::iterator i;
		Object *Child = GetFirstChild (i);
		return Child->GetYAlign ();
	} else {
		gcpWidgetData* pData = (gcpWidgetData*) g_object_get_data (G_OBJECT (GetWidget ()), "data");
		ArtDRect rect;
		pData->GetObjectBounds (this, &rect);
		return (rect.y1 - rect.y0) / 2.;
	}
}

void gcpDocument::SetTheme (gcpTheme *theme)
{
	if (m_Theme)
		m_Theme->RemoveClient (this);
	m_Theme = theme;
	m_Theme->AddClient (this);
	m_BondLength = theme->GetBondLength ();
	m_BondAngle = theme->GetBondAngle ();
	m_ArrowLength = theme->GetArrowLength ();
	m_TextFontFamily = theme->GetTextFontFamily ();
	m_TextFontStyle = theme->GetTextFontStyle ();
	m_TextFontWeight = theme->GetTextFontWeight ();
	m_TextFontVariant = theme->GetTextFontVariant ();
	m_TextFontStretch = theme->GetTextFontStretch ();
	m_TextFontSize = theme->GetTextFontSize ();
	pango_attr_list_unref (m_PangoAttrList);
	m_PangoAttrList = pango_attr_list_new ();
	pango_attr_list_insert (m_PangoAttrList, pango_attr_family_new (theme->GetFontFamily ()));
	pango_attr_list_insert (m_PangoAttrList, pango_attr_style_new (theme->GetFontStyle ()));
	pango_attr_list_insert (m_PangoAttrList, pango_attr_weight_new (theme->GetFontWeight ()));
	pango_attr_list_insert (m_PangoAttrList, pango_attr_stretch_new (theme->GetFontStretch ()));
	pango_attr_list_insert (m_PangoAttrList, pango_attr_variant_new (theme->GetFontVariant ()));
	if (m_pView)
		m_pView->UpdateTheme ();
}

bool gcpDocument::OnSignal (SignalId Signal, Object *Child)
{
	if (Signal == OnThemeChangedSignal) {
		m_BondLength = m_Theme->GetBondLength ();
		m_BondAngle = m_Theme->GetBondAngle ();
		m_ArrowLength = m_Theme->GetArrowLength ();
		m_TextFontFamily = m_Theme->GetTextFontFamily ();
		m_TextFontStyle = m_Theme->GetTextFontStyle ();
		m_TextFontWeight = m_Theme->GetTextFontWeight ();
		m_TextFontVariant = m_Theme->GetTextFontVariant ();
		m_TextFontStretch = m_Theme->GetTextFontStretch ();
		m_TextFontSize = m_Theme->GetTextFontSize ();
		pango_attr_list_unref (m_PangoAttrList);
		m_PangoAttrList = pango_attr_list_new ();
		pango_attr_list_insert (m_PangoAttrList, pango_attr_family_new (m_Theme->GetFontFamily ()));
		pango_attr_list_insert (m_PangoAttrList, pango_attr_style_new (m_Theme->GetFontStyle ()));
		pango_attr_list_insert (m_PangoAttrList, pango_attr_weight_new (m_Theme->GetFontWeight ()));
		pango_attr_list_insert (m_PangoAttrList, pango_attr_stretch_new (m_Theme->GetFontStretch ()));
		pango_attr_list_insert (m_PangoAttrList, pango_attr_variant_new (m_Theme->GetFontVariant ()));
		m_pView->UpdateTheme ();
	}
	return false;
}

void gcpDocument::SetDirty (bool isDirty)
{
	if (!m_Window)
		return;
	char *title = g_strdup_printf ((isDirty? "*%s": "%s"), GetTitle ());
	m_Window->SetTitle (title);
	g_free (title);
	Document::SetDirty (isDirty);
}

void gcpDocument::SetLabel(const gchar* label)
{
	m_label = g_strdup (label);
	m_Window->SetTitle (label);
}

void gcpDocument::OnThemeNamesChanged ()
{
	gcpDocPropDlg *dlg = dynamic_cast <gcpDocPropDlg *> (GetDialog ("properties"));
	if (dlg)
		dlg->OnThemeNamesChanged ();
}

double gcpDocument::GetMedianBondLength ()
{
	vector <double> lengths;
	int max = 128, nb = 0;
	lengths.reserve (max);
	double result = 0.;
	stack<map< string, Object * >::iterator> iters;
	map< string, Object * >::iterator m;
	Object *Cur = this, *Ob = GetFirstChild (m);
	while (Ob) {
		if (Ob->GetType () == BondType) {
			if (nb == max) {
				max += 128;
				lengths.resize (max);
			}
			lengths[nb++] = static_cast <gcpBond *> (Ob)->Get2DLength ();
		} else if (Ob->HasChildren ()) {
			Cur = Ob;
			iters.push (m);
			Ob = Cur->GetFirstChild (m);
			continue;
		}
		Ob = Cur->GetNextChild (m);
		while (!Ob && !iters.empty ()) {
			m = iters.top ();
			iters.pop ();
			Cur = Cur->GetParent ();
			Ob = Cur->GetNextChild (m);
		}
	}
	if (nb > 0)
#ifdef HAVE_VECTOR_DATA
		go_range_median_inter_nonconst (lengths.data (), nb, &result);
#else
		go_range_median_inter_nonconst (&(*lengths.begin ()), nb, &result);
#endif
	return result;
}


syntax highlighted by Code2HTML, v. 0.9.1