/* -*- C++ -*-

Copyright (C) 2000-2002 Francesco Bresciani <fbresciani@users.sourceforge.net>
This file is part of Kemistry

Kemistry 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, or (at your option)
any later version.

Kemistry 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 Kemistry; see the file COPYING.  If not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/


#include <qstringlist.h>
#include <qfile.h>
#include <qtextstream.h>

#include <kdebug.h>
#include <kstddirs.h>
#include <kstaticdeleter.h>

#include "kmolcalcdata.h"

static const char * dataversion = "1";

KMolCalcSubUnit::KMolCalcSubUnit() : _name("<noname>"),
  _symbol("<nosymbol>"), _mass(0), _userDefined(false)
{
}

KMolCalcSubUnit::KMolCalcSubUnit(const QString& name, const QString& symbol)
  : _name(name), _symbol(symbol), _mass(0), _userDefined(false)
{
}

double KMolCalcSubUnit::mass()
{
  return _mass;
}

KMolCalcSubUnit * KMolCalcSubUnit::createFromDomElement
(const QDomElement & element)
{
  if (element.tagName() == "element")
    return new KMolCalcElement(element);

  if (element.tagName() == "group")
    return new KMolCalcGroup(element);
  return 0L;
}

KMolCalcElement::KMolCalcElement(const QString& name, const QString& symbol,
                                 double mass)
  : KMolCalcSubUnit(name, symbol)
{
  _mass = mass;
}

KMolCalcElement::KMolCalcElement(const QDomElement &element)
{
  for (QDomNode node = element.firstChild(); !node.isNull();
          node = node.nextSibling())
  {
      QDomElement el = node.toElement();
      //the child is not an element;
      if (el.isNull())
          continue;
      if (el.tagName() == "name")
          _name = el.text();
      if (el.tagName() == "symbol")
          _symbol = el.text();
      if (el.tagName() == "mass")
          _mass = el.text().toDouble();
  }
}

QDomElement KMolCalcElement::toDomElement(QDomDocument& document)
{
  QDomElement elementElement = document.createElement("element");
  QDomText text;
  
  //create the "symbol" element, for example <symbol>H</symbol>
  QDomElement tempElement = document.createElement("symbol");
  text = document.createTextNode(_symbol);
  tempElement.appendChild(text);
  elementElement.appendChild(tempElement);

  //create the "name" element, for example <name>Hydrogen</name>
  tempElement = document.createElement("name");
  text = document.createTextNode(_name);
  tempElement.appendChild(text);
  elementElement.appendChild(tempElement);

  //create the "mass" element
  tempElement = document.createElement("mass");
  text = document.createTextNode(QString::number(_mass));
  tempElement.appendChild(text);
  elementElement.appendChild(tempElement);
  
  return elementElement;
}

unsigned int KMolCalcElement::elementCount(const QString& symbol)
{
  if (_symbol == symbol)
    return 1;
  else
    return 0;
}

KMolCalcGroup::KMolCalcGroup(const QString &name, const QString &symbol,
                             const QString &formula,
                             const QString &parsedFormula)
  : KMolCalcSubUnit(name, symbol)
{
  _formula = formula;
  _parsedFormula = parsedFormula;
}

KMolCalcGroup::KMolCalcGroup(const QDomElement &element)
{

    for (QDomNode node = element.firstChild(); !node.isNull();
          node = node.nextSibling())
    {
      QDomElement el = node.toElement();
      //the child is not an element;
      if (el.isNull())
          continue;
      if (el.tagName() == "name")
          _name = el.text();
      if (el.tagName() == "symbol")
          _symbol = el.text();
      if (el.tagName() == "formula")
          _formula = el.text();
      if (el.tagName() == "pformula")
          _parsedFormula = el.text();
  }

    
  QStringList stringList = QStringList::split("|", _parsedFormula);
  QStringList::Iterator it;

  for (it = stringList.begin(); it != stringList.end(); ++it)
  {
    int index = (*it).find("-");
    if (index == -1)
      continue;
    QString symbol = (*it).left(index);
    double count = (*it).mid(index+1).toDouble();
    _elementsCount[symbol] = count;
  }
}

double KMolCalcGroup::mass()
{
  if (_mass == 0.0)
    {
      double temp = 0.0;
      // Iterate trough the elementsCount map and for any element in the map
      // retrieve the corresponding KMolCalcElement* from data dictionary
      QMap<QString, double>::Iterator it;
      for ( it = _elementsCount.begin(); it != _elementsCount.end(); ++it)
        {
          KMolCalcSubUnit *subunit = KMolCalcData::pointer()->data(it.key());
          if (subunit)
            {
              KMolCalcElement *element =
                dynamic_cast<KMolCalcElement*>(subunit);
              if (element)
                {
                  double elementMass = element->mass();
                  temp += elementMass * it.data();
                }
            }
        }
      _mass = temp;
      return _mass;
    }
  else
    return _mass;
}

double KMolCalcGroup::elementCount(const QString& symbol)
{
  if (_elementsCount.contains(symbol))
    return _elementsCount[symbol];
  else
    return 0;
}

QDomElement KMolCalcGroup::toDomElement(QDomDocument &document)
{
   QDomElement groupElement = document.createElement("group");
   QDomText text;
  
  //create the "symbol" element, for example <symbol>H</symbol>
  QDomElement tempElement = document.createElement("symbol");
  text = document.createTextNode(_symbol);
  tempElement.appendChild(text);
  groupElement.appendChild(tempElement);

  //create the "name" element, for example <name>Hydrogen</name>
  tempElement = document.createElement("name");
  text = document.createTextNode(_name);
  tempElement.appendChild(text);
  groupElement.appendChild(tempElement);

  //create the "formula" element
  tempElement = document.createElement("formula");
  text = document.createTextNode(_formula);
  tempElement.appendChild(text);
  groupElement.appendChild(tempElement);


  //create the "pformula" element
  tempElement = document.createElement("pformula");
  text = document.createTextNode(_parsedFormula);
  tempElement.appendChild(text);
  groupElement.appendChild(tempElement);

  
  return groupElement;
}

KMolCalcData* KMolCalcData::s_self = 0L;
static KStaticDeleter<KMolCalcData> sd;

KMolCalcData::KMolCalcData() : _editable(true), _modified(false)
{
  _subUnitsData = QDict<KMolCalcSubUnit>(131, true);
  _subUnitsData.setAutoDelete(true);

  // Try to load data from the global datafile, which should be
  // <kde_datadir>/kemistry/elements.xml
  QString filename = locate("data", "kmolcalc/kmolcalcdata.xml");
  if (KStandardDirs::exists(filename))
      loadDataFile(filename, false);

  // Try to locate data from the local datafile (if exists)
  filename = locateLocal("data", "kmolcalc/kmolcalcdata_user.xml");
  if (KStandardDirs::exists(filename))
    loadDataFile(filename, true);
}

KMolCalcData::~KMolCalcData()
{
  saveData();
}

void KMolCalcData::saveData()
{
  if (modified())
  {
    // one or more KMolCalcSubUnits (Element, Group, ...)
    //in the database has been modified, removed or added
    // We must write a private copy of the XML data file in the user
    // home directory
    QString filename = locateLocal("data", "kmolcalc/kmolcalcdata_user.xml");
    saveUserDataFile(filename);
  }
}

KMolCalcData* KMolCalcData::pointer()
  {
    if (!s_self)
    	sd.setObject(s_self, new KMolCalcData);

    return s_self;
  }


void KMolCalcData::loadDataFile(const QString& filename, bool local)
{
  kdDebug()<<"Loading "<<((local) ? "local" : "")<<"  data file '"
           <<filename<<"'"<<endl;
  QDomDocument datafileDoc;
  QFile f(filename);
  f.open(IO_ReadOnly);
  datafileDoc.setContent(&f);
  f.close();

  QDomNodeList documentChildList = datafileDoc.childNodes();
  for (unsigned int i = 0; i <= documentChildList.count(); i++)
    {
      QDomElement domElement = documentChildList.item(i).toElement();
      if (!domElement.isNull() && domElement.tagName() == "kmolcalc")
        {
            //read version
            QString version = domElement.attribute("version");
            //version attribute not used yet
          QDomNodeList childList = domElement.childNodes();
          for (unsigned int j = 0; j <= childList.count(); j++)
            {
              QDomElement subunitElement = childList.item(j).toElement();
              if (!subunitElement.isNull())
                {
                  KMolCalcSubUnit *subunit = KMolCalcSubUnit::
                    createFromDomElement(subunitElement);
                  if (subunit)
                    {
                      subunit->setUserDefined(local);
                      _subUnitsData.insert(subunit->symbol(), subunit);
                    }
                }
            }
        }
    }
  kdDebug()<<"Loaded "<<_subUnitsData.count()<<" data"<<endl;
}

void KMolCalcData::saveUserDataFile(const QString& filename)
{
  kdDebug()<<"Saving local data file '"<<filename<<"'"<<endl;

  // we create a QDomDocument from the SubUnits dictionary
  // The dictionary will be traversed with an iterator and, for
  // every user-defined (m_userDefined==true) SubUnit  in the
  // the dictionary the method "toDomElement() is called
  QDomDocument dataFileDoc("kmolcalc");
  QDomElement kmolcalcElement = dataFileDoc.createElement("kmolcalc");
  //add version information
  kmolcalcElement.setAttribute("version", dataversion);

  QDictIterator<KMolCalcSubUnit> it(_subUnitsData);
  QDomElement subunitElement;
  while (it.current())
    {
      if (it.current()->userDefined())
        {
          subunitElement = it.current()->toDomElement(dataFileDoc);
          kmolcalcElement.appendChild(subunitElement);
        }

      ++it;
    }

  dataFileDoc.appendChild(kmolcalcElement);

  QFile f(filename);
  f.open(IO_WriteOnly);
  QTextStream stream(&f);
  stream<<dataFileDoc;
  f.close();

}

KMolCalcSubUnit* KMolCalcData::data(const QString& symbol) const
{
  return _subUnitsData.find(symbol);
}

const QDict<KMolCalcSubUnit>& KMolCalcData::dataDictionary() const
{
  return _subUnitsData;
}

void KMolCalcData::removeSubUnit(const QString& symbol)
{
  if (editable())
    {
      _subUnitsData.remove(symbol);
      setModified(true);
    }
  else
    kdDebug() << "KMolCalcData::removeSubUnit(const QString&): "
              << "Attempt to remove an element from the subunit dictionary while "
              << "KMolCalcData is in read-only mode!"<<endl;

}


void KMolCalcData::addSubUnit(const QString& symbol, KMolCalcSubUnit* subunit)
{
  if (editable())
    {
      _subUnitsData.insert(symbol, subunit);
      setModified(true);
    }
  else
    kdDebug() << "KMolCalcDatar::addSubUnit(const QString&, KMolCalcSubunit*): "
              << "Attempt to add an element to the subunit dictionary while "
              << "KMolCalcData is in read-only mode!"<<endl;
}

KMolCalcElement* KMolCalcData::element(unsigned int atomicNumber) const
{
  if (atomicNumber > 103 || atomicNumber == 0)
    return 0L;

  static const char* symbols[]
    = { "", "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", "Na",
        "Mg", "Al", "Si", "P", "S", "Cl", "Ar", "K", "Ca", "Sc", "Ti",
        "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As",
        "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru",
        "Rh", "Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe", "Cs",
        "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd", "Tb", "Dy",
        "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir",
        "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra",
        "Ac", "Th", "Pa", "U", "Np", "Pu", "Am", "Cm", "Bk", "Cf", "Es",
        "Fm", "Md", "No", "Lr"
    };

  return dynamic_cast<KMolCalcElement*>(
    KMolCalcData::pointer()->data(symbols[atomicNumber]));
}


