/* -*- C++ -*-

Copyright (C) 2000-2001 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 <kglobal.h>
#include <kdebug.h>
#include <klocale.h>
#include <kstddirs.h>

#include <qdom.h>
#include <qfile.h>
#include <qregexp.h>
#include <qtextstream.h>

#include "kmolcalcparser.h"
#include "mol.h"
#include "fileformat.h"
#include "data.h"
#include <fstream>

QMap<QString, double>
KMolCalcParser::substituteGroup(const QMap<QString, double>& groupMap)
{
// This function, given a QMap<QString, double> returned by parseString,
// returns a new QMap in which every group is substituted by its
// consituting elements.
// Example:
// Consider the dimethyl ether Met20.
// The QMap returned by parseString is
// [O]--> 1.0
// [Met] --> 2.0
// if you use this function the QMap returned is
// [O]--> 1.0
// [C]--> 2.0
// [H]--> 6.0
QMap<QString, double> elementMap;
QMap<QString, double>::ConstIterator it;
QMap<QString, double>::Iterator localIt;


for (it = groupMap.begin(); it != groupMap.end(); ++it)
{
  KMolCalcSubUnit* subunit = KMolCalcData::pointer()->data(it.key());
  if (subunit)
  {
    KMolCalcGroup* group = dynamic_cast<KMolCalcGroup*>(subunit);
    if (group)
    {
      QMap<QString, double> localGroupMap = group->elementMap();
      for (localIt = localGroupMap.begin();
           localIt != localGroupMap.end(); ++localIt)
      {
        elementMap[localIt.key()] = elementMap[localIt.key()]
					+ localIt.data() * groupMap[it.key()];
      }
	  
    }

    KMolCalcElement* element = dynamic_cast<KMolCalcElement*>(subunit);
    if (element)
    {
      elementMap[element->symbol()] = elementMap[element->symbol()]
                                      + groupMap[element->symbol()];
    }
  }
}
return elementMap;
}

KMolCalcParser::KMolCalcParser()
{}

KMolCalcParser::~KMolCalcParser()
{
  KMolCalcData::pointer()->saveData();
}


bool KMolCalcParser::parseString(const QString& formula,
QMap<QString, double>& count)
{
  QMap<QString, double> tempCount;
  QMap<QString, double>::Iterator it;

  QString s;
  double prefix, suffix;

  _error = QString::null;
  // If no data are loaded, throw an exception because a formula
  // cannot be parsed!!!
  if (KMolCalcData::pointer()->dataCount()==0)
  {
    _error = i18n("Cannot parse formula. Element data not loaded."
                  "Please check Kemistry installation");
    return false;
  }

  if (formula.isEmpty())
    return true;

  s = formula.simplifyWhiteSpace();

  // remove spaces
  s.replace(QRegExp(" "), "");

  int i = 0;
  int sl = s.length();
  bool ok = true;
  // find a "prefix" number
  // for example, if the string to be parsed is
  // 2.3H2O than prefix=2.3 and the molecular mass returned will be
  while ( i < sl && ( s[i].isDigit() || s[i] == '.'))
    i++;
  prefix = (i == 0 ? 1.0 : s.left(i).toDouble(&ok));
  if (!ok || i == sl || prefix == 0)
  {
    _error = i18n("Found a syntax error while searching for"
             " a floating point prefix");
    return false;
  }

  while(i < sl)
  {
    int j = i;
    if (s[i] == '(')
    {
      int level = 1; // tempCount level of nested parentheses
      while (true)
      {
        if (++i == sl)
        {
          _error = i18n("The number of opened parentheses is different than"
                        " the number of closed parentheses");
          return false;
        }
        if (s[i] == '(')
          level++;
        if (s[i] == ')')
          level--;
        if (level == 0) // found matching parentheses
          break;
      }
      QMap<QString, double> subStringCount;
      parseString(s.mid(j+1, i-j-1), subStringCount);
      j = ++i;
      // search for floating point suffix: Mg(OH)2 --> suffix = 2
      //
      while ( i < sl && (s[i].isDigit() || s[i] == '.'))
        i++;
      suffix = (i == j ? 1.0 : s.mid(j, i-j).toDouble(&ok));
      if (!ok || suffix == 0.0)
      {
        _error = i18n("Found an error while searching for"
                      " the stoichiometric coefficient of a group"
                      " enclosed by parentheses");
        return false;
      }
      for (it = subStringCount.begin(); it != subStringCount.end(); ++it)
        tempCount[it.key()] = tempCount[it.key()] +  suffix * it.data();
    } // if (s[i] == '(')
    else if (s[i].category() == QChar::Letter_Uppercase)
      // symbols begin with an uppercase char!
    {
      while ( ++i < sl && s[i].category() == QChar::Letter_Lowercase );
      QString elname = s.mid(j, i-j);
      j = i;
      while ( i < sl && (s[i] .isDigit() || s[i] == '.'))
        i++;
      suffix = (i == j ? 1 : s.mid(j, i-j).toDouble(&ok));
      if (!ok || suffix == 0)
      {
        _error = i18n("Found an error while searching for"
                      " the stoichiometric coefficient of a group"
                      " enclosed by parentheses");
        return false;
      }
      KMolCalcSubUnit* group = KMolCalcData::pointer()->data(elname);
      if (!group)
      {
        _error = i18n("Undefined symbol found: %1").arg(elname);
        return false;
      }
      tempCount[group->symbol()] = tempCount[group->symbol()] + suffix;
    }
    else
      if (s[i] == '+')
      {
        if (tempCount.isEmpty())
        {
          _error = i18n("Syntax error");
          return false;
        }
        for (it = tempCount.begin(); it != tempCount.end(); ++it)
        {
          count[it.key()] = count[it.key()] + it.data() *prefix;
        }
        bool result = parseString(s.mid(i+1, sl-i-1), count);
        if (!result)
          return false;
      }
      else
      {
        _error = i18n("Syntax error");
        return false;
      }
  }
  // multiply by the prefix

  for (it = tempCount.begin(); it != tempCount.end(); ++it)
  {
    count[it.key()] = count[it.key()] + it.data() *prefix;
  }

  return true;
}

void KMolCalcParser::parseFormula(const QString& formula, bool onlyElements)
{
  QMap<QString, double> count;
  parseString(formula, count);
  if (onlyElements)
    _subUnitsCount = substituteGroup(count);
  else
    _subUnitsCount = count;
}

QString KMolCalcParser::parseFormulaToString(const QString& formula, bool onlyElements)
{
  QMap<QString, double> count;
  parseString(formula, count);
  if (onlyElements)
    count = substituteGroup(count);
  QString parsedFormula;

  QMap<QString, double>::Iterator it;
  for (it = count.begin(); it != count.end(); ++it)
  {
    parsedFormula += it.key();
    parsedFormula += "-";
    parsedFormula += QString::number(it.data());
    parsedFormula += "|";
  }

  // remove the latest "|"
  if (parsedFormula.right(1) == "|")
    return parsedFormula.left(parsedFormula.length()-1);
  else
    return parsedFormula;
}



double KMolCalcParser::computeMass()
{
  if (_subUnitsCount.isEmpty())
  {
    _error = i18n("You must parse a formula before computing a"
                  " molecular mass");
    return 0;
  }

  // compute the mass
  double mass = 0;
  QMap<QString, double>::Iterator it;
  for (it = _subUnitsCount.begin(); it != _subUnitsCount.end(); ++it)
    mass += KMolCalcData::pointer()->data(it.key())->mass() * it.data();

  return mass;
}

void KMolCalcParser::
computeMassAndElemPercent(double &mass, QMap<QString, double>& percent)
{
  QMap<QString, double>::Iterator it;

  mass = computeMass();

  QMap<QString, double> elementCount = substituteGroup(_subUnitsCount);
  percent.clear();
  for (it = elementCount.begin(); it != elementCount.end(); ++it)
    percent[it.key()] = it.data() * KMolCalcData::pointer()->
		data(it.key())->mass() / mass * 100;
}

void KMolCalcParser::parseFile(const QString& filename)
{
	OpenBabel::OBFileFormat ff;
	OpenBabel::OBMol mol;
	
	if (!OpenBabel::extab.CanReadExtension(const_cast<char*>
				( filename.latin1() ) ) )
	{
		_error = i18n("Cannot read file '%1'. Extension not recognised")
			.arg(filename);
	}
	else
		mol.SetInputType(OpenBabel::extab.FilenameToType(const_cast<char*>
				( filename.latin1() ) ) );
	std::ifstream ifs;
	ifs.open(filename);

	if (!ifs)
	{
		_error = i18n("Cannot open file '%1'").arg(filename);
		return;
	}

	else
	{
		ff.ReadMolecule(ifs, mol);
		ifs.close();
	}

	//count atoms
	QMap<QString, double> count;

	for (int i = 1; i <= mol.NumAtoms(); i++)
	{
		OpenBabel::OBAtom* atom = mol.GetAtom(i);
		count[OpenBabel::etab.GetSymbol(atom->GetAtomicNum())] += 1;
	}

	_subUnitsCount = count;
}

