// -*- C++ -*-

/* 
 * Gnome Chemisty Utils
 * crystaldoc.cc 
 *
 * Copyright (C) 2002-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
 */

#include "config.h"
#include "crystaldoc.h"
#include "crystalview.h"
#include "xml-utils.h"
#include <gtk/gtk.h>
#include <libintl.h>
#include <glib/gi18n-lib.h>
#include <cstring>
#include <vector>
#include <clocale>
#include <cmath>

#define __max(x,y)  ((x) > (y)) ? (x) : (y)
#define __min(x,y)  ((x) < (y)) ? (x) : (y)
#define PREC 1e-3

using namespace gcu;

gchar *gcu::LatticeName[] = {
	(gchar*) "simple cubic",
	(gchar*) "body-centered cubic",
	(gchar*) "face-centered cubic",
	(gchar*) "hexagonal",
	(gchar*) "tetragonal",
	(gchar*) "body-centered tetragonal",
	(gchar*) "orthorhombic",
	(gchar*) "base-centered orthorhombic",
	(gchar*) "body-centered orthorhombic",
	(gchar*) "face-centered orthorhombic",
	(gchar*) "rhombohedral",
	(gchar*) "monoclinic",
	(gchar*) "base-centered monoclinic",
	(gchar*) "triclinic"
};

CrystalDoc::CrystalDoc (Application *App): GLDocument (App)
{
}


CrystalDoc::~CrystalDoc()
{
	while (!AtomDef.empty ()) {
		delete AtomDef.front ();
		AtomDef.pop_front ();
	}
	while (!Atoms.empty ()) {
		delete Atoms.front ();
		Atoms.pop_front ();
	}
	while (!LineDef.empty ()) {
		delete LineDef.front ();
		LineDef.pop_front ();
	}
	while (!Lines.empty ()) {
		delete Lines.front ();
		Lines.pop_front ();
	}
	while (!Cleavages.empty ()) {
		delete Cleavages.front ();
		Cleavages.pop_front ();
	}
	while (!m_Views.empty ()) {
		m_Views.pop_back ();
	}
}

void CrystalDoc::Reinit()
{
	//destruction of lists
	while (!AtomDef.empty ()) {
		delete AtomDef.front ();
		AtomDef.pop_front ();
	}
	while (!Atoms.empty ()) {
		delete Atoms.front ();
		Atoms.pop_front ();
	}
	while (!LineDef.empty ()) {
		delete LineDef.front ();
		LineDef.pop_front ();
	}
	while (!Lines.empty ()) {
		delete Lines.front ();
		Lines.pop_front ();
	}
	while (!Cleavages.empty ()) {
		delete Cleavages.front ();
		Cleavages.pop_front ();
	}
	Init ();
}

void CrystalDoc::Init()
{
	m_a = m_b = m_c = 100;
	m_alpha = m_beta = m_gamma = 90;
	m_lattice = cubic;
	m_xmin = m_ymin = m_zmin = 0;
	m_xmax = m_ymax = m_zmax = 1;
	m_bFixedSize = false;
	m_MaxDist = 0;
	if (m_Views.size() == 0)
	{
		CrystalView* pView = CreateNewView();
		m_Views.push_back(pView);
	}
}

void CrystalDoc::ParseXMLTree (xmlNode* xml)
{
	char *old_num_locale, *txt;
	xmlNodePtr node;
	bool bViewLoaded = false;

	Reinit ();
	old_num_locale = g_strdup (setlocale (LC_NUMERIC, NULL));
	setlocale (LC_NUMERIC, "C");
	//look for generator node
	unsigned version = 0xffffff , major, minor, micro;
	node = xml->children;
	while (node) {
		if (!strcmp ((const char*)(node->name), "generator"))
			break;
		node = node->next;
	}
	if (node) {
		txt = (char*) xmlNodeGetContent (node);
		if (sscanf(txt, "Gnome Crystal %d.%d.%d", &major, &minor, &micro) == 3)
			version = micro + minor * 0x100 + major * 0x10000;
		xmlFree (txt);
	}
	node = xml->children;
	while(node) {
		if (!strcmp ((gchar*) node->name, "text"));
		else if (!strcmp ((gchar*) node->name, "lattice")) {
			txt = (char*) xmlNodeGetContent (node);
			int i = 0;
			while (strcmp (txt, LatticeName[i]) && (i < 14))
				i++;
			if (i < 14)
				m_lattice = (gcLattices)i;
			xmlFree (txt);
		} else if (!strcmp ((gchar*) node->name, "cell")) {
			txt = (char*) xmlGetProp (node, (xmlChar*)"a");
			if (txt) {
				sscanf (txt, "%lg", &m_a);
				xmlFree (txt);
			}
			txt = (char*) xmlGetProp (node, (xmlChar*) "b");
			if (txt) {
				sscanf (txt, "%lg", &m_b);
				xmlFree (txt);
			}
			txt = (char*) xmlGetProp (node, (xmlChar*) "c");
			if (txt) {
				sscanf (txt, "%lg", &m_c);
				xmlFree (txt);
			}
			txt = (char*) xmlGetProp (node, (xmlChar*) "alpha");
			if (txt) {
				sscanf (txt, "%lg", &m_alpha);
				xmlFree (txt);
			}
			txt = (char*) xmlGetProp (node, (xmlChar*) "beta");
			if (txt) {
				sscanf(txt, "%lg", &m_beta);
				xmlFree (txt);
			}
			txt = (char*) xmlGetProp (node, (xmlChar*) "gamma");
			if (txt) {
				sscanf(txt, "%lg", &m_gamma);
				xmlFree (txt);
			}
		} else if (!strcmp ((gchar*) node->name, "size")) {
			ReadPosition (node, "start", &m_xmin, &m_ymin, &m_zmin);
			ReadPosition (node, "end", &m_xmax, &m_ymax, &m_zmax);
			txt = (char*) xmlGetProp (node, (xmlChar*) "fixed");
			if (txt) {
				if (!strcmp (txt, "true"))
					m_bFixedSize = true;
				xmlFree (txt);
			}
		} else if (!strcmp ((gchar*) node->name, "atom")) {
			CrystalAtom *pAtom = CreateNewAtom ();
			if (pAtom->Load (node))
				AtomDef.push_back (pAtom);
			else
				delete pAtom;
		} else if (!strcmp ((gchar*) node->name, "line")) {
			CrystalLine *pLine = CreateNewLine ();
			if (pLine->Load (node))
				LineDef.push_back (pLine);
			else
				delete pLine;
		} else if (!strcmp ((gchar*) node->name, "cleavage")) {
			CrystalCleavage *pCleavage = CreateNewCleavage ();
			if (pCleavage->Load (node))
				Cleavages.push_back (pCleavage);
			else
				delete pCleavage;
		} else if (!strcmp ((gchar*) node->name, "view")) {
			if (!bViewLoaded) {
				m_Views.front ()->Load (node); //the first view is created with the document
				bViewLoaded = true;
			} else
				LoadNewView (node);
		}
		node = node->next;
	}
	setlocale (LC_NUMERIC, old_num_locale);
	g_free (old_num_locale);
	SetDirty (false);
	Update ();
}

bool CrystalDoc::LoadNewView(xmlNodePtr node)
{
	return true;
}

CrystalView *CrystalDoc::GetView()
{
	if (m_Views.size() == 0)
	{
		CrystalView* pView = CreateNewView();
		m_Views.push_back(pView);
	}
	return m_Views.front();
}

void CrystalDoc::Update()
{
	m_Empty = (AtomDef.empty() && LineDef.empty()) ? true : false;
	CrystalAtom Atom;
	CrystalLine Line;
	gdouble alpha = m_alpha * M_PI / 180;
	gdouble beta = m_beta * M_PI / 180;
	gdouble gamma = m_gamma * M_PI / 180;
	// Remove all atoms
	while (!Atoms.empty())
	{
		delete Atoms.front();
		Atoms.pop_front();
	}
	// Remove all bonds
	while (!Lines.empty())
	{
		delete Lines.front();
		Lines.pop_front();
	}

	////////////////////////////////////////////////////////////
	//Establish list of atoms
	CrystalAtomList::iterator i, iend = AtomDef.end ();
	
	for (i = AtomDef.begin(); i != iend; i++) {
		Duplicate(**i);
		switch (m_lattice) {
		case body_centered_cubic:
		case body_centered_tetragonal:
		case body_centered_orthorhombic:
			Atom = **i;
			Atom.Move(0.5, 0.5, 0.5);
			Duplicate(Atom);
			break;
		case face_centered_cubic:
		case face_centered_orthorhombic:
			Atom = **i;
			Atom.Move(0.5, 0, 0.5);
			Duplicate(Atom);
			Atom = **i;
			Atom.Move(0, 0.5, 0.5);
			Duplicate(Atom);
		case base_centered_orthorhombic:
		case base_centered_monoclinic:
			Atom = **i;
			Atom.Move(0.5, 0.5, 0);
			Duplicate(Atom);
			break;
		default:
			break;
		}
	}
	
	////////////////////////////////////////////////////////////
	//Establish list of atoms
	CrystalLineList::iterator j, jend = LineDef.end();
	for (j = LineDef.begin() ; j != jend ; j++)
	{
		switch ((*j)->Type())
		{
		case edges:
			Line = **j;
			Line.SetPosition(0 ,0, 0, 1, 0, 0);
			Duplicate(Line);
			Line.SetPosition(0 ,0, 0, 0, 1, 0);
			Duplicate(Line);
			Line.SetPosition(0 ,0, 0, 0, 0, 1);
			Duplicate(Line);
			break ;
		case diagonals:
			Line = **j;
			Line.SetPosition(0 ,0, 0, 1, 1, 1);
			Duplicate(Line);
			Line.SetPosition(1 ,0, 0, 0, 1, 1);
			Duplicate(Line);
			Line.SetPosition(0 ,1, 0, 1, 0, 1);
			Duplicate(Line);
			Line.SetPosition(1 ,1, 0, 0, 0, 1);
			Duplicate(Line);
			break ;
		case medians:
			Line = **j;
			Line.SetPosition(.5, .5, 0, .5, .5, 1);
			Duplicate(Line);
			Line.SetPosition(0, .5, .5, 1, .5, .5);
			Duplicate(Line);
			Line.SetPosition(.5, 0, .5, .5, 1, .5);
			Duplicate(Line);
			break ;
		case normal:
			Duplicate(**j) ;
			switch (m_lattice)
			{
			case body_centered_cubic:
			case body_centered_tetragonal:
			case body_centered_orthorhombic:
				Line = **j;
				Line.Move(0.5, 0.5, 0.5);
				Duplicate(Line);
				break;
			case face_centered_cubic:
			case face_centered_orthorhombic:
				Line = **j;
				Line.Move(0.5, 0, 0.5);
				Duplicate(Line);
				Line = **j;
				Line.Move(0, 0.5, 0.5);
				Duplicate(Line);
			case base_centered_orthorhombic:
			case base_centered_monoclinic:
				Line = **j;
				Line.Move(0.5, 0.5, 0);
				Duplicate(Line);
				break;
			default:
				break;
			}
			break ;
		case unique:
			if (((*j)->Xmin() >= m_xmin) && ((*j)->Xmax() <= m_xmax)
				&& ((*j)->Ymin() >= m_ymin) && ((*j)->Ymax() <= m_ymax)
				&& ((*j)->Zmin() >= m_zmin) && ((*j)->Zmax() <= m_zmax))
					Lines.push_back(new CrystalLine(**j)) ;
		}
	}

	//Manage cleavages
	CrystalCleavageList::iterator k;
	for (k = Cleavages.begin(); k != Cleavages.end(); k++)
	{
		double x;
		std::vector<double> ScalarProducts;
		std::vector<double>::iterator m;
		unsigned n;

		//scalar products calculus and storing
		for (i = Atoms.begin(); i != Atoms.end(); i++)
		{
			x = (*i)->ScalProd((*k)->h(), (*k)->k(), (*k)->l());
			for (m = ScalarProducts.begin(); m != (ScalarProducts.end()) && ((*m) > (x + PREC)); m++) ;
			if ((m == ScalarProducts.end()) || (fabs(*m - x) > PREC)) ScalarProducts.insert(m, x);
		}

		//cleave atoms and bonds
		if ((n = (*k)->Planes()) >= ScalarProducts.size())
		{
			GtkWidget* message = gtk_message_dialog_new(NULL, (GtkDialogFlags) 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Everything has been cleaved"));
			g_signal_connect (G_OBJECT (message), "response", G_CALLBACK (gtk_widget_destroy), NULL);
			gtk_widget_show(message);
			for (i = Atoms.begin(); i != Atoms.end(); i++) (*i)->Cleave();
			for (j = Lines.begin(); j != Lines.end(); j++) (*j)->Cleave();
		}
		else
		{
			x = ScalarProducts[n - 1];
			for (i = Atoms.begin(); i != Atoms.end(); i++)
				if (x < (*i)->ScalProd((*k)->h(), (*k)->k(), (*k)->l())+ PREC)
					(*i)->Cleave() ;

			//bonds cleavage
			for (j = Lines.begin(); j != Lines.end(); j++)
				if (x < (*j)->ScalProd((*k)->h(), (*k)->k(), (*k)->l())+ PREC)
					(*j)->Cleave();
		}

		ScalarProducts.clear() ;
	}
	
	//Transform coordinates to Cartesians and find center of visible view
	gdouble x, y, z, d,
		xmin = G_MAXDOUBLE, ymin = G_MAXDOUBLE, zmin = G_MAXDOUBLE,
		xmax = -G_MAXDOUBLE, ymax = -G_MAXDOUBLE, zmax = -G_MAXDOUBLE;
	iend = Atoms.end ();
	for (i = Atoms.begin(); i != iend; i++) {
		(*i)->NetToCartesian(m_a, m_b, m_c, alpha, beta, gamma);
		if ((*i)->IsCleaved ())
			continue;
		(*i)->GetCoords (&x, &y, &z);
		if (x < xmin)
			xmin = x;
		if (y < ymin)
			ymin = y;
		if (z < zmin)
			zmin = z;
		if (x > xmax)
			xmax = x;
		if (y > ymax)
			ymax = y;
		if (z > zmax)
			zmax = z;
	}
	jend = Lines.end ();
	for (j = Lines.begin (); j != jend; j++) {
		(*j)->NetToCartesian (m_a, m_b, m_c, alpha, beta, gamma);
		if ((*j)->IsCleaved ())
			continue;
		x = (*j)->Xmin ();
		y = (*j)->Ymin ();
		z = (*j)->Zmin ();
		if (x < xmin)
			xmin = x;
		if (y < ymin)
			ymin = y;
		if (z < zmin)
			zmin = z;
		x = (*j)->Xmax ();
		y = (*j)->Ymax ();
		z = (*j)->Zmax ();
		if (x > xmax)
			xmax = x;
		if (y > ymax)
			ymax = y;
		if (z > zmax)
			zmax = z;
	}

	//Searching the center of the crystal and find maximum distance from center
	x = (xmin + xmax) / 2.;
	y = (ymin + ymax) / 2.;
	z = (zmin + zmax) / 2.;
	m_MaxDist = 0;
	for (i = Atoms.begin(); i != iend; i++) {
		d =  (*i)->Distance (x, y, z, m_bFixedSize);
		m_MaxDist = __max (m_MaxDist, d);
		(*i)->Move (- x, - y, - z);
	}

	for (j = Lines.begin(); j != Lines.end(); j++) {
		d =  (*j)->Distance (x, y, z, m_bFixedSize);
		m_MaxDist = __max (m_MaxDist, d);
		(*j)->Move (- x, - y, - z);
	}
}

void CrystalDoc::Duplicate(CrystalAtom& Atom)
{
	CrystalAtom AtomX, AtomY, AtomZ ;
	AtomX = Atom ;
	AtomX.Move(- floor(AtomX.x()-m_xmin), - floor(AtomX.y()-m_ymin), - floor(AtomX.z()-m_zmin)) ;
	while (AtomX.x() <= m_xmax)
	{
		AtomY = AtomX ;
		while (AtomY.y() <= m_ymax)
		{
			AtomZ = AtomY ;
			while (AtomZ.z() <= m_zmax)
			{
				Atoms.push_back(new CrystalAtom(AtomZ)) ;
				AtomZ.Move(0,0,1) ;
			}
			AtomY.Move(0,1,0) ;
		}
		AtomX.Move(1,0,0) ;
	}
}

void CrystalDoc::Duplicate(CrystalLine& Line)
{
	CrystalLine LineX, LineY, LineZ ;
	LineX = Line ;
	LineX.Move(- floor(LineX.Xmin()-m_xmin), - floor(LineX.Ymin()-m_ymin), - floor(LineX.Zmin()-m_zmin)) ;
	while (LineX.Xmax() <= m_xmax)
	{
		LineY = LineX ;
		while (LineY.Ymax() <= m_ymax)
		{
			LineZ = LineY ;
			while (LineZ.Zmax() <= m_zmax)
			{
				Lines.push_back(new CrystalLine(LineZ)) ;
				LineZ.Move(0,0,1) ;
			}
			LineY.Move(0,1,0) ;
		}
		LineX.Move(1,0,0) ;
	}
}

void CrystalDoc::Draw()
{
	CrystalAtomList::iterator i;
	for (i = Atoms.begin(); i != Atoms.end(); i++) (*i)->Draw();
	CrystalLineList::iterator j;
	for (j = Lines.begin(); j != Lines.end(); j++) (*j)->Draw();
}

CrystalView* CrystalDoc::CreateNewView()
{
	return new CrystalView(this);
}

CrystalAtom* CrystalDoc::CreateNewAtom()
{
	return new CrystalAtom();
}

CrystalLine* CrystalDoc::CreateNewLine()
{
	return new CrystalLine();
}

CrystalCleavage* CrystalDoc::CreateNewCleavage()
{
	return new CrystalCleavage();
}

const char* CrystalDoc::GetProgramId()
{
	return NULL;
}

xmlDocPtr CrystalDoc::BuildXMLTree()
{
	gchar buf[256];
	xmlDocPtr xml;
	xmlNodePtr node;
	xmlNsPtr ns;
	char *old_num_locale;

	xml = xmlNewDoc((xmlChar*)"1.0");
	if (xml == NULL) {throw(1);}
	
	old_num_locale = g_strdup(setlocale(LC_NUMERIC, NULL));
	setlocale(LC_NUMERIC, "C");
	xmlDocSetRootElement (xml,  xmlNewDocNode(xml, NULL, (xmlChar*)"crystal", NULL));
	ns = xmlNewNs (xml->children, (xmlChar*) "http://www.nongnu.org/gcrystal", (xmlChar*) "gcry");
	xmlSetNs (xml->children, ns);

	try
	{
		node = xmlNewDocNode(xml, NULL, (xmlChar*)"generator", (xmlChar*)GetProgramId());
		if (node) xmlAddChild(xml->children, node); else throw (int) 0;
		
		node = xmlNewDocNode(xml, NULL, (xmlChar*)"lattice", (xmlChar*)LatticeName[m_lattice]);
		if (node) xmlAddChild(xml->children, node); else throw (int) 0;
	
		node = xmlNewDocNode(xml, NULL, (xmlChar*)"cell", NULL);
		if (node) xmlAddChild(xml->children, node); else throw (int) 0;
		snprintf(buf, sizeof(buf), "%g", m_a);
		xmlNewProp(node, (xmlChar*)"a", (xmlChar*)buf);
		snprintf(buf, sizeof(buf), "%g", m_b);
		xmlNewProp(node, (xmlChar*)"b", (xmlChar*)buf);
		snprintf(buf, sizeof(buf), "%g", m_c);
		xmlNewProp(node, (xmlChar*)"c", (xmlChar*)buf);
		snprintf(buf, sizeof(buf), "%g", m_alpha);
		xmlNewProp(node, (xmlChar*)"alpha", (xmlChar*)buf);
		snprintf(buf, sizeof(buf), "%g", m_beta);
		xmlNewProp(node, (xmlChar*)"beta", (xmlChar*)buf);
		snprintf(buf, sizeof(buf), "%g", m_gamma);
		xmlNewProp(node, (xmlChar*)"gamma", (xmlChar*)buf);
	
		node = xmlNewDocNode(xml, NULL, (xmlChar*)"size", NULL);
		if (node) xmlAddChild(xml->children, node); else throw (int) 0;
		WritePosition(xml, node, "start", m_xmin, m_ymin, m_zmin);
		WritePosition(xml, node, "end", m_xmax, m_ymax, m_zmax);
		if (m_bFixedSize)
			xmlNewProp (node, (xmlChar *) "fixed", (xmlChar *) "true");
		
		CrystalAtomList::iterator i;
		for (i = AtomDef.begin(); i != AtomDef.end(); i++)
		{
			node = (*i)->Save(xml);
			if (node) xmlAddChild(xml->children, node); else throw (int) 0;
		}
	
		CrystalLineList::iterator j;
		for (j = LineDef.begin(); j != LineDef.end(); j++)
		{
			node = (*j)->Save(xml);
			if (node) xmlAddChild(xml->children, node); else throw (int) 0;
		}
	
		CrystalCleavageList::iterator k;
		for (k = Cleavages.begin(); k != Cleavages.end(); k++)
		{
			node = (*k)->Save(xml);
			if (node) xmlAddChild(xml->children, node); else throw (int) 0;
		}
		
		list<CrystalView*>::iterator view;
		for (view = m_Views.begin(); view != m_Views.end(); view++)
		{
			node = (*view)->Save(xml);
			if (node) xmlAddChild(xml->children, node); else throw (int) 0;
		}
	
		setlocale(LC_NUMERIC, old_num_locale);
		g_free(old_num_locale);

		return xml;
	}
	catch (int num)
	{
		xmlFreeDoc(xml);
		setlocale(LC_NUMERIC, old_num_locale);
		g_free(old_num_locale);
		return NULL;
	}
}


syntax highlighted by Code2HTML, v. 0.9.1