/* 
 * Gnome Chemistry Utils
 * element.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 "element.h"
#include "xml-utils.h"
#include <libxml/parser.h>
#include <libxml/parserInternals.h>
#include <libxml/xmlmemory.h>
#include <glib.h>
#include <libintl.h>
#include <glib/gi18n-lib.h>
#include <goffice/utils/go-math.h>
#include <clocale>
#include <cmath>
#include <cstring>
#include <vector>
#include <map>
#include <set>
#include <string>

static set<string>units;

static void ReadValue (char const *source, GcuValue &value)
{
	char *buf, *dot;
	value.value = strtod (source, &buf);
	dot = strchr (source, '.');
	value.prec = (dot)? buf - dot - 1: 0;
	value.delta = (*buf == '(')? strtol (buf + 1, NULL, 10): 0;
}

static void ReadDimensionalValue (char const *source, GcuDimensionalValue &value)
{
	char *buf, *dot;
	value.value = strtod (source, &buf);
	dot = strchr (source, '.');
	value.prec = (dot)? buf - dot - 1: 0;
	value.delta = (*buf == '(')? strtol (buf + 1, NULL, 10): 0;
}

namespace gcu
{

class EltTable
{
public:
	EltTable();
	virtual ~EltTable();
	
	Element* operator [] (int Z);
	Element* operator [] (string Symbol);
	
	void AddElement(Element* Elt);

private:
	vector<Element*> Elements;
	map <string, Element*> EltsMap;
};

} // namespace gcu

using namespace gcu;

EltTable Table;

EltTable::EltTable()
{
	bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
#ifdef ENABLE_NLS
	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
#endif
	xmlDocPtr xml;
	char* DefaultName;
	char *lang = getenv ("LANG");
	setlocale (LC_ALL, lang);
	char *old_num_locale, *buf, *num, *dot;
	unsigned char Z;
	map <string, string> Langs;
	Langs["de"] = _("German");
	Langs["fr"] = _("French");
	Langs["it"] = _("Italian");
	Langs["pl"] = _("Polish");
	Langs["ru"] = _("Russian");
	if (!(xml = xmlParseFile (PKGDATADIR"/elements.xml")))
	{
		g_error (_("Can't find and read elements.xml"));
	}
	old_num_locale = g_strdup (setlocale (LC_NUMERIC, NULL));
	setlocale (LC_NUMERIC, "C");
	xmlNode* node = xml->children, *child;
	if (strcmp ((const char*) node->name, "gpdata")) g_error (_("Incorrect file format: elements.xml"));
	node = node->children;
	Element* Elt;
	while (node)
	{
		if (strcmp ((const char*) node->name, "text"))
		{
			if (strcmp ((const char*) node->name, "element")) g_error (_("Incorrect file format: elements.xml"));
			buf = (char*) xmlGetProp (node, (xmlChar*) "symbol");
			num = (char*) xmlGetProp (node, (xmlChar*) "Z");
			Elt = new Element (Z = atoi (num), buf);
			xmlFree (num);
			xmlFree (buf);
			num = (char*) xmlGetProp (node, (xmlChar*) "max_bonds");
			Elt->m_MaxBonds = atoi (num);
			xmlFree (num);
			num = (char*) xmlGetProp (node, (xmlChar*) "weight");
			Elt->m_Weight = strtod (num, &buf);
			dot = strchr (num, '.');
			Elt->m_WeightPrec = (dot)? buf - dot - 1: 0;
			child = node->children;
			DefaultName = NULL;
			while (child)
			{
				if (!strcmp ((const char*) child->name, "text")) {
					child = child->next;
					continue;
				}
				if (!strcmp((const char*)child->name, "name")) {
					buf = (char*) xmlNodeGetLang (child);
					if ((buf) && (lang)){
						string Lang = Langs[buf];
						char *Name = (char*) xmlNodeGetContent (child);
						if (Lang.length ())
							Elt->names[Lang] = Name;
						if (!strncmp (lang, buf, 2))
							Elt->name = Name;
						xmlFree (Name);
					} else if (!buf) {
						DefaultName = (char*) xmlNodeGetContent (child);
						Elt->names[_("English")] = DefaultName;
					}
					xmlFree (buf);
				} else if (!strcmp ((const char*) child->name, "color")) {
					buf = (char*) xmlGetProp (child, (xmlChar*) "red");
					if (buf) {
						Elt->m_DefaultColor[0] = strtod (buf, NULL);
						xmlFree (buf);
					}
					buf = (char*) xmlGetProp (child, (xmlChar*) "green");
					if (buf) {
						Elt->m_DefaultColor[1] = strtod (buf, NULL);
						xmlFree (buf);
					}
					buf = (char*) xmlGetProp (child, (xmlChar*) "blue");
					if (buf) {
						Elt->m_DefaultColor[2] = strtod (buf, NULL);
						xmlFree (buf);
					}
				}				child = child->next;
			}
			if ((Elt->name.length () == 0) && DefaultName) Elt->name = DefaultName;
			if (DefaultName)
				xmlFree (DefaultName);
			AddElement (Elt);
		}
		node = node->next;
	}
	setlocale (LC_NUMERIC, old_num_locale);
	g_free (old_num_locale);
	xmlFreeDoc (xml);
}

EltTable::~EltTable()
{
	map<string, Element*>::iterator i;
	for (i = EltsMap.begin(); i != EltsMap.end(); i++)
		if ((*i).second) delete (*i).second;
	EltsMap.clear();
	Elements.clear();
}

Element* EltTable::operator[](int Z)
{
	return Elements[Z];
}

Element* EltTable::operator[](string Symbol)
{
	return EltsMap[Symbol];
}

void EltTable::AddElement(Element* Elt)
{
	if ((unsigned) Elt->GetZ() >= Elements.size()) Elements.resize(Elements.size() + 10);
	Elements[Elt->GetZ()] = Elt;
	EltsMap[Elt->GetSymbol()] = Elt;
}

Element::Element(int Z, const char* Symbol)
{
	m_Z = Z;
	strncpy(m_Symbol, Symbol, 3);
	m_Symbol[3] = 0;
	m_MaxBonds = 0;
	m_BestSide = true;
	switch (m_Z)
	{
		case 6:
		case 14:
		case 32:
			m_DefaultValence = 4;
			break;
		case 5:
		case 7:
		case 13:
		case 15:
		case 33:
		case 51:
			m_DefaultValence = 3;
			break;
		case 8:
		case 16:
		case 34:
		case 52:
			m_BestSide = false;
		case 4:
			m_DefaultValence = 2;
			break;
		case 9:
		case 17:
		case 35:
		case 53:
			m_BestSide = false;
			m_DefaultValence = 1;
			break;
		case 2:
		case 10:
		case 28:
		case 36:
		case 54:
			m_DefaultValence = 0;
			break;
		default:
			m_DefaultValence = -1;
	}
	m_DefaultColor[0] = m_DefaultColor[1] = m_DefaultColor[2] = 0.0;
	if (m_Z <= 2) {
		m_nve = m_tve = m_Z;
		m_maxve = 2;
	} else if (m_Z <= 10) {
		m_nve = m_tve = m_Z - 2;
		m_maxve = 8;
	} else if (m_Z <= 18) {
		m_nve = m_tve = m_Z - 10;
		m_maxve = 8;
	} else if (m_Z <= 29) {
		m_nve = m_tve = m_Z - 18;
		m_maxve = 18;
	} else if (m_Z <= 36) {
		m_tve = m_Z - 18;
		m_nve = m_tve - 10;
		m_maxve = 18;
	} else if (m_Z <= 47) {
		m_nve = m_tve = m_Z - 36;
		m_maxve = 18;
	} else if (m_Z <= 54) {
		m_tve = m_Z - 36;
		m_nve = m_tve - 10;
		m_maxve = 18;
	} else if (m_Z <= 70) {
		m_nve = m_tve = m_Z - 54;
		m_maxve = 32;
	} else if (m_Z <= 79) {
		m_tve = m_Z - 54;
		m_nve = m_tve - 14;
		m_maxve = 32;
	} else if (m_Z <= 86) {
		m_tve = m_Z - 54;
		m_nve = m_tve - 24;
		m_maxve = 32;
	} else if (m_Z <= 102) {
		m_nve = m_tve = m_Z - 86;
		m_maxve = 32;
	} else if (m_Z <= 111) {
		m_tve = m_Z - 86;
		m_nve = m_tve - 14;
		m_maxve = 32;
	} else { // Assume m_Z <= 118
		m_tve = m_Z - 86;
		m_nve = m_tve - 24;
		m_maxve = 32;
	}
}

Element::~Element()
{
	while (!m_radii.empty()) {
		delete m_radii.back();
		m_radii.pop_back();
	}
	while (!m_en.empty()) {
		delete m_en.back();
		m_en.pop_back();
	}
	while (!m_isotopes.empty ()) {
		delete (m_isotopes.back ());
		m_isotopes.pop_back ();
	}
	map<string, Value*>::iterator i, iend = props.end ();
	for (i = props.begin (); i != iend; i++)
		delete (*i).second;
	props.clear ();
}

const gchar* Element::Symbol(gint Z)
{
	Element* Elt = Table[Z];
	return (Elt)? Elt->GetSymbol(): NULL;
}

bool Element::BestSide(gint Z)
{
	Element* Elt = Table[Z];
	return (Elt)? Elt->GetBestSide(): true;
}

gint Element::Z(const gchar* symbol)
{
	Element* Elt = Table[symbol];
	return (Elt)? Elt->GetZ(): 0;
}

Element* Element::GetElement(gint Z)
{
	return Table[Z];
}

Element* Element::GetElement(const gchar* symbol)
{
	return Table[symbol];
}

unsigned Element::GetMaxBonds(gint Z)
{
	Element* Elt = Table[Z];
	return (Elt)? Elt->GetMaxBonds(): 0;
}

bool Element::GetRadius(GcuAtomicRadius* radius)
{
	Element* Elt = Table[radius->Z];
	if (!Elt || !Elt->m_radii.size ())
		return false;
	for (int i = 0; Elt->m_radii[i]; i++)
	{
		if (radius->type != Elt->m_radii[i]->type) continue;
		if (radius->charge != Elt->m_radii[i]->charge) continue;
		if ((radius->cn >= 0) &&(radius->cn != Elt->m_radii[i]->cn)) continue;
		if ((radius->spin != GCU_N_A_SPIN) &&(radius->spin != Elt->m_radii[i]->spin)) continue;
		if (!radius->scale)
		{
			*radius = *Elt->m_radii[i];
			return true;
		}
		else if (!strcmp(radius->scale, Elt->m_radii[i]->scale))
		{
			radius->value = Elt->m_radii[i]->value;
			return true;
		}
	}
	return false;
}

bool Element::GetElectronegativity(GcuElectronegativity* en)
{
	Element* Elt = Table[en->Z];
	if (!Elt) return false;
	if (!en->scale)
	{
		*en = *Elt->m_en[0];
		return true;
	}
	for (int i = 0; Elt->m_en[i]; i++)
		if (!strcmp(en->scale, Elt->m_en[i]->scale))
		{
			en->value = Elt->m_en[i]->value;
			return true;
		}

		return false;
}

const GcuAtomicRadius** Element::GetRadii()
{
	return (const GcuAtomicRadius**) &m_radii.front();
}

const GcuElectronegativity** Element::GetElectronegativities()
{
	return (const GcuElectronegativity**) &m_en.front();
}

double Element::GetWeight (int Z, int &prec)
{
	Element* Elt = Table[Z];
	return (Elt)? Elt->GetWeight(prec): 0.;
}

void Element::LoadRadii ()
{
	xmlDocPtr xml;
	char *old_num_locale, *buf, *num;
	unsigned char Z;
	static bool loaded = false;
	if (loaded)
		return;
	if (!(xml = xmlParseFile (PKGDATADIR"/radii.xml")))
	{
		g_error (_("Can't find and read radii.xml"));
	}
	old_num_locale = g_strdup (setlocale (LC_NUMERIC, NULL));
	setlocale (LC_NUMERIC, "C");
	xmlNode* node = xml->children, *child;
	if (strcmp ((const char*) node->name, "gpdata")) g_error (_("Incorrect file format: radii.xml"));
	node = node->children;
	Element* Elt;
	set<string>::iterator it = units.find ("pm");
	if (it == units.end ()) {
		units.insert ("pm");
		it = units.find ("pm");
	}
	while (node) {
		if (strcmp ((const char*) node->name, "text"))
		{
			if (strcmp ((const char*) node->name, "element")) g_error (_("Incorrect file format: radii.xml"));
			num = (char*) xmlGetProp (node, (xmlChar*) "Z");
			Elt = Table[Z = atoi (num)];
			child = node->children;
			while (child)
			{
				if (!strcmp ((const char*) child->name, "text")) {
					child = child->next;
					continue;
				}
				if (!strcmp ((const char*) child->name, "radius")) {
					GcuAtomicRadius* radius = new GcuAtomicRadius;
					radius->Z = Z;	//FIXME: is it really useful there?
					buf = (char*) xmlGetProp (child, (xmlChar*) "type");
					if (!buf ||
						((!((!strcmp (buf, "covalent")) && (radius->type = GCU_COVALENT))) &&
						(!((!strcmp (buf, "vdW")) && (radius->type = GCU_VAN_DER_WAALS))) &&
						(!((!strcmp (buf, "ionic")) && (radius->type = GCU_IONIC))) &&
						(!((!strcmp (buf, "metallic")) && (radius->type = GCU_METALLIC))) &&
						(!((!strcmp (buf, "atomic")) && ((radius->type = GCU_ATOMIC) || true))))) {
						//invalid radius
						delete radius;
						if (buf)
							xmlFree (buf);
						continue;
					}
					buf = (char*) xmlGetProp (child, (xmlChar*) "scale");
					if (buf) {
						radius->scale = g_strdup (buf);
						xmlFree (buf);
					} else
						radius->scale = NULL;
					buf = (char*) xmlGetProp (child, (xmlChar*) "charge");
					if (buf) {
						radius->charge = strtol (buf, NULL, 10);
						xmlFree (buf);
					} else
						radius->charge = 0;
					buf = (char*) xmlGetProp (child, (xmlChar*) "cn");
					if (buf) {
						radius->cn = strtol (buf, NULL, 10);
						xmlFree (buf);
					} else
						radius->cn = -1;
					buf = (char*) xmlGetProp (child, (xmlChar*)"spin");
					if ((!buf) ||
						(!((!strcmp (buf, "low")) && (radius->spin = GCU_LOW_SPIN))) &&
						(!((!strcmp (buf, "high")) && (radius->spin = GCU_HIGH_SPIN))))
						radius->spin = GCU_N_A_SPIN;
					if (buf)
						xmlFree (buf);
					buf = (char*) xmlGetProp (child, (xmlChar*) "value");
					if (buf) {
						ReadDimensionalValue (buf, radius->value) ;
						radius->value.unit = (*it).c_str ();
						Elt->m_radii.push_back (radius);
						xmlFree (buf);
					} else
						delete radius;
				} else
					g_error ("Invalid radius node");
				child = child->next;
			}
			Elt->m_radii.push_back (NULL);
		}
		node = node->next;
	}
	setlocale (LC_NUMERIC, old_num_locale);
	g_free (old_num_locale);
	xmlFreeDoc (xml);
	loaded = true;
}

void Element::LoadElectronicProps ()
{
	xmlDocPtr xml;
	char *old_num_locale, *buf, *num, *dot, *end;
	unsigned char Z;
	unsigned i;
	static bool loaded = false;
	if (loaded)
		return;
	if (!(xml = xmlParseFile (PKGDATADIR"/elecprops.xml")))
	{
		g_error (_("Can't find and read elecprops.xml"));
	}
	old_num_locale = g_strdup (setlocale (LC_NUMERIC, NULL));
	setlocale (LC_NUMERIC, "C");
	xmlNode* node = xml->children, *child;
	if (strcmp ((const char*) node->name, "gpdata")) g_error (_("Incorrect file format: elecprops.xml"));
	node = node->children;
	Element* Elt;
	while (node) {
		if (strcmp ((const char*) node->name, "text")) {
			if (strcmp ((const char*) node->name, "element")) g_error (_("Incorrect file format: elecprops.xml"));
			num = (char*) xmlGetProp (node, (xmlChar*) "Z");
			Elt = Table[Z = atoi (num)];
			child = node->children;
			while (child) {
				if (!strcmp ((const char*) child->name, "text")) {
					child = child->next;
					continue;
				}
				if (!strcmp ((const char*) child->name, "en")) {
					GcuElectronegativity* en = new GcuElectronegativity;
					en->Z = Z;	//FIXME: is it really useful there?
					buf = (char*) xmlGetProp (child, (xmlChar*) "scale");
					if (buf) {
						en->scale = GetStaticScale (buf);
						xmlFree (buf);
					} else
						en->scale = NULL;
					buf = (char*) xmlGetProp (child, (xmlChar*) "value");
					if (buf) {
						en->value.value = strtod (buf, &end);
						dot = strchr (buf, '.');
						en->value.prec = (dot)? end - dot - 1: 0;
						en->value.delta = 0; // we should use a generic parser
						Elt->m_en.push_back (en);
						xmlFree (buf);
					} else
						delete en;	//without a value, the structure is useless and is discarded
				} else if (!strcmp ((const char*) child->name, "config")) {
					buf = (char*) xmlNodeGetContent (child);
					char *cur = buf;
					bool nonvoid = false;
					if (buf[0] == '[') {
						Elt->ElecConfig.append (buf, 4);
						cur += 4;
						nonvoid = true;
					}
					while (cur && *cur) {
						if (nonvoid) {
							cur++;
							Elt->ElecConfig.append (" ");
						}
						Elt->ElecConfig.append (cur, 2);
						cur += 2;
						i = 1;
						while (cur[i] > ' ')
							i++;
						Elt->ElecConfig.append ("<sup>");
						Elt->ElecConfig.append (cur, i);
						Elt->ElecConfig.append ("</sup>");
						cur += i;
					}
					Elt->ElecConfig.append (" ");
					xmlFree (buf);
				} else if (!strcmp ((const char*) child->name, "ei")) {
					unsigned rank;
					buf = (char*) xmlGetProp (child, (xmlChar*) "rank");
					if (buf) {
						rank = strtol (buf, NULL, 10);
						xmlFree (buf);
					} else
						rank = 1;
					if ((i = Elt->m_ei.size ()) < rank) {
						Elt->m_ei.resize (rank);
						for (; i < rank; i++)
							Elt->m_ei[i].value = go_nan;
					}
					rank--;
					buf = (char*) xmlGetProp (child, (xmlChar*) "value");
					if (buf) {
						ReadValue (buf, (GcuValue&)Elt->m_ei[rank]) ;
						xmlFree (buf);
					} else {
						//no need to read the unit
						Elt->m_ei[rank].value = go_nan;
						break;
					}
					buf = (char*) xmlGetProp (child, (xmlChar*) "unit");
					if (buf) {
						string str(buf);
						set<string>::iterator it = units.find (str);
						if (it == units.end ()) {
							units.insert (str);
							it = units.find (str);
						}
						Elt->m_ei[rank].unit = (*it).c_str ();
						xmlFree (buf);
					} else
						Elt->m_ei[rank].unit = "MJ.mol<sup>-1</sup>";
				} else if (!strcmp ((const char*) child->name, "ae")) {
					unsigned rank;
					buf = (char*) xmlGetProp (child, (xmlChar*) "rank");
					if (buf) {
						rank = strtol (buf, NULL, 10);
						xmlFree (buf);
					} else
						rank = 1;
					if ((i = Elt->m_ae.size ()) < rank) {
						Elt->m_ae.resize (rank);
						for (; i < rank; i++)
							Elt->m_ae[i].value = go_nan;
					}
					rank--;
					buf = (char*) xmlGetProp (child, (xmlChar*) "value");
					if (buf) {
						ReadValue (buf, (GcuValue&)Elt->m_ae[rank]) ;
						xmlFree (buf);
					} else {
						//no need to read the unit
						Elt->m_ae[rank].value = go_nan;
						break;
					}
					buf = (char*) xmlGetProp (child, (xmlChar*) "unit");
					if (buf) {
						string str(buf);
						set<string>::iterator it = units.find (str);
						if (it == units.end ()) {
							units.insert (str);
							it = units.find (str);
						}
						Elt->m_ae[rank].unit = (*it).c_str ();
						xmlFree (buf);
					} else
						Elt->m_ae[rank].unit = "kJ.mol<sup>-1</sup>";
				} else
					g_error ("Invalid property node");
				child = child->next;
			}
			Elt->m_en.push_back (NULL);
		}
		node = node->next;
	}
	setlocale (LC_NUMERIC, old_num_locale);
	g_free (old_num_locale);
	xmlFreeDoc (xml);
	loaded = true;
}

void Element::LoadIsotopes ()
{
	xmlDocPtr xml;
	char *old_num_locale, *num;
	unsigned char Z;
	static bool loaded = false;
	if (loaded)
		return;
	if (!(xml = xmlParseFile (PKGDATADIR"/isotopes.xml")))
	{
		g_error (_("Can't find and read isotopes.xml"));
	}
	old_num_locale = g_strdup (setlocale (LC_NUMERIC, NULL));
	setlocale (LC_NUMERIC, "C");
	xmlNode* node = xml->children, *child;
	if (strcmp ((const char*) node->name, "gpdata")) g_error (_("Incorrect file format: isotopes.xml"));
	node = node->children;
	Element *Elt;
	Isotope *Is;
	int minA, maxA, niso;
	while (node) {
		if (strcmp ((const char*) node->name, "text")) {
			if (strcmp ((const char*) node->name, "element")) g_error (_("Incorrect file format: isotopes.xml"));
			minA = maxA = niso = 0;
			num = (char*) xmlGetProp (node, (xmlChar*) "Z");
			Elt = Table[Z = atoi (num)];
			xmlFree (num);
			if (Elt == NULL)	// This should not occur
				continue;
			child = node->children;
			while (child) {
				if (!strcmp ((const char*) child->name, "text")) {
					child = child->next;
					continue;
				}
				if (!strcmp ((const char*) child->name, "isotope")) {
					Is = new Isotope ();
					num = (char*) xmlGetProp (child, (xmlChar*) "A");
					if (num) {
						Is->A = strtol (num, NULL, 10);
						xmlFree (num);
					}
					num = (char*) xmlGetProp (child, (xmlChar*) "weight");
					if (num) {
						ReadValue (num, Is->mass);
						xmlFree (num);
					}
					num = (char*) xmlGetProp (child, (xmlChar*) "abundance");
					if (num) {
						ReadValue (num, Is->abundance);
						xmlFree (num);
						niso++;
						if (minA == 0)
							minA = maxA = Is->A;
						else {
							if (minA > Is->A)
								minA = Is->A;
							else if (maxA < Is->A)
								maxA = Is->A;
						}
					}
					Elt->m_isotopes.push_back (Is);
				}
				child = child->next;
			}
			if (minA > 0) {
				IsotopicPattern *pattern = new IsotopicPattern (minA, maxA);
				vector<Isotope*>::iterator i, iend = Elt->m_isotopes.end ();
				for (i = Elt->m_isotopes.begin (); i != iend; i++) {
					if ((*i)->abundance.value != 0.)
						pattern->SetValue ((*i)->A, (*i)->abundance.value);
				}
				pattern->Normalize ();
				niso = pattern->GetMonoNuclNb ();
				i = Elt->m_isotopes.begin ();
				while ((*i)->A != niso)
					i++;
				pattern->SetMonoMass ((*i)->mass.value);
				Elt->m_patterns.push_back (pattern);
			}
		}
		node = node->next;
	}
	setlocale (LC_NUMERIC, old_num_locale);
	g_free (old_num_locale);
	xmlFreeDoc (xml);
	loaded = true;
}

void Element::LoadAllData ()
{
	LoadRadii ();
	LoadElectronicProps ();
	LoadIsotopes ();
	LoadBODR ();
}

IsotopicPattern *Element::GetIsotopicPattern (unsigned natoms)
{
	if (m_patterns.size () == 0)
		return NULL;
	IsotopicPattern *pat, *pattern, *result = NULL;
	if (natoms == 0)
		return NULL;
	unsigned i = 1;
	while ((natoms & 1) == 0) {
		natoms >>= 1;
		i++;
	}
	while (natoms) {
		if (i == 1) {
			result = m_patterns[0];
			result->Ref ();
		} else if (natoms & 1) {
			while (m_patterns.size () < i) {
				pat = m_patterns[m_patterns.size () - 1]->Square ();
				pattern = pat->Simplify ();
				pat->Unref ();
				m_patterns.push_back (pattern);
			}
			pattern = m_patterns[i - 1];
			if (result) {
				pat = result->Multiply (*pattern);
				result->Unref ();
				result = pat->Simplify ();
				pat->Unref ();
			} else {
				result = pattern;
				result->Ref ();
			}
		}
		natoms >>= 1;
		i++;
	}
	return result;
}

GcuDimensionalValue const *Element::GetIonizationEnergy (unsigned rank)
{
	return (rank <= m_ei.size ())? &m_ei[rank - 1]: NULL;
}

GcuDimensionalValue const *Element::GetElectronAffinity (unsigned rank)
{
	return (rank <= m_ae.size ())? &m_ae[rank - 1]: NULL;
}

void Element::LoadBODR ()
{
	char *old_num_locale;
	old_num_locale = g_strdup (setlocale (LC_NUMERIC, NULL));
	setlocale (LC_NUMERIC, "C");
	xmlDocPtr xml = xmlParseFile (BODR_PKGDATADIR"/elements.xml");
	if (!xml)
		return;
	xmlNodePtr node = xml->children, child;
	char *buf = NULL, *unit;
	Element *elt;
	map <string, Value*> props;
	if (!strcmp ((char const*) node->name, "list")) {
		node = node->children;
		while (node) {
			if (!strcmp ((char const*) node->name, "atom")) {
				elt = NULL;
				child = node->children;
				while (child) {
					if (!strcmp ((char const*) child->name, "scalar")) {
						Value *val;
						buf = (char*) xmlGetProp (child, (xmlChar const*) "dataType");
						if (!strcmp (buf, "xsd:float")) {
							char *end, *dot;
							xmlFree (buf);
							buf = (char*) xmlNodeGetContent (child);
							double x = strtod (buf, &end);
							dot = strchr (buf, '.');
							int prec = (dot)? end - dot - 1: 0;
							unit = (char*) xmlGetProp (child, (xmlChar const*) "units");
							xmlFree (buf);
							buf = (char*) xmlGetProp (child, (xmlChar const*) "errorValue");
							int delta;
							if (buf)
								delta = strtol (buf, NULL, 10);
							else
								delta = 0;
							xmlFree (buf);
							if (unit) {
								DimensionalValue *v = new DimensionalValue ();
								if (!strcmp (unit, "units:atmass"))
									v->val.unit = "u";
								else if (!strcmp (unit, "units:ev"))
									v->val.unit = "eV";
								else if (!strcmp (unit, "units:ang"))
									v->val.unit = "Å";
								else if (!strcmp (unit, "siUnits:kelvin"))
									v->val.unit = "K";
//								else if (!strcmp (unit, ""))
//									v->val.unit = "";
								else
									v->val.unit = "";
								xmlFree (unit);
								v->val.value = x;
								v->val.prec = prec;
								v->val.delta = delta;
								val = v;
							} else {
								SimpleValue *v = new SimpleValue ();
								v->val.value = x;
								v->val.prec = prec;
								v->val.delta = delta;
								val = v;
							}
							buf = (char*) xmlGetProp (child, (xmlChar const*) "dictRef");
							if (elt)
								elt->props[(strncmp (buf, "bo:", 3))? buf: buf + 3] = val;
							xmlFree (buf);
						} else if (!strcmp (buf, "xsd:String") || !strcmp (buf, "xsd:string")) {
							buf = (char*) xmlGetProp (child, (xmlChar const*) "dictRef");
							char *val = (char*) xmlNodeGetContent (child);
							if (elt)
								elt->sprops[(strncmp (buf, "bo:", 3))? buf: buf + 3] = val;
							xmlFree (buf);
							xmlFree (val);
						} else if (!strcmp (buf, "xsd:int") || !strcmp (buf, "xsd:Integer")) {
							xmlFree (buf);
							buf = (char*) xmlNodeGetContent (child);
							int val = strtol (buf, NULL, 10);
							xmlFree (buf);
							buf = (char*) xmlGetProp (child, (xmlChar const*) "dictRef");
							if (!strcmp (buf, "bo:atomicNumber"))
								elt = Table[val];
							else if (elt)
								elt->iprops[(strncmp (buf, "bo:", 3))? buf: buf + 3] = val;
							xmlFree (buf);
						} else if (!strcmp (buf, "xsd:date")) {
							// assumeing only the discovery year is useful for us
							xmlFree (buf);
							buf = (char*) xmlNodeGetContent (child);
							int val = strtol (buf, NULL, 10);
							xmlFree (buf);
							buf = (char*) xmlGetProp (child, (xmlChar const*) "dictRef");
							if (!strcmp (buf, "bo:discoveryDate"))
								elt->iprops["discoveryDate"] = val;
							xmlFree (buf);
						}
					}
					if (props.size () > 0) {
						map <string, Value*>:: iterator i, iend = props.end ();
						for (i = props.begin (); i != iend; i++)
							elt->props[(*i).first] = (*i).second;
						props.clear ();
					}
					child = child->next;
				}
			}
			node = node->next;
		}
	}
	xmlFreeDoc (xml);
	setlocale (LC_NUMERIC, old_num_locale);
	g_free (old_num_locale);
}

int Element::GetIntegerProperty (char const *property_name)
{
	map<string, int>::iterator i = iprops.find (property_name);
	return (i == iprops.end ())? GCU_ERROR: (*i).second;
}


syntax highlighted by Code2HTML, v. 0.9.1