/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2010-2012 University of Bonn. All rights reserved.
 * 
 *
 *   This file is part of MoleCuilder.
 *
 *    MoleCuilder 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.
 *
 *    MoleCuilder 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 MoleCuilder.  If not, see .
 */
/*
 * Formula.cpp
 *
 *  Created on: Jul 21, 2010
 *      Author: crueger
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "CodePatterns/MemDebug.hpp"
#include "Formula.hpp"
#include 
#include "World.hpp"
#include "Element/periodentafel.hpp"
#include "Element/element.hpp"
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/Range.hpp"
using namespace std;
Formula::Formula() :
  numElements(0)
{}
Formula::Formula(const Formula &src) :
  elementCounts(src.elementCounts),
  numElements(src.numElements)
{}
Formula::Formula(const string &formula) :
  numElements(0)
{
  fromString(formula);
}
Formula::~Formula()
{}
Formula &Formula::operator=(const Formula &rhs){
  // No self-assignment check needed
  elementCounts=rhs.elementCounts;
  numElements=rhs.numElements;
  return *this;
}
std::string Formula::toString() const{
  stringstream sstr;
  for(const_iterator iter=end();iter!=begin();){
    --iter;
    sstr << (*iter).first->getSymbol();
    if((*iter).second>1)
      sstr << (*iter).second;
  }
  return sstr.str();
}
void Formula::fromString(const std::string &formula) throw(FormulaStringParseException){
  // make this transactional, in case an error is thrown
  Formula res;
  string::const_iterator begin = formula.begin();
  string::const_iterator end = formula.end();
  res.parseFromString(begin,end,static_cast(0));
  (*this)=res;
}
int Formula::parseMaybeNumber(std::string::const_iterator &it,string::const_iterator &end) throw(FormulaStringParseException){
  static const range Numbers = makeRange('0',static_cast('9'+1));
  int count = 0;
  while(it!=end && Numbers.isInRange(*it))
    count = (count*10) + ((*it++)-Numbers.first);
  // one is implicit
  count = (count!=0)?count:1;
  return count;
}
void Formula::parseFromString(std::string::const_iterator &it,string::const_iterator &end,char delimiter) throw(FormulaStringParseException){
  // some constants needed for parsing... Assumes ASCII, change if other encodings are used
  static const range CapitalLetters = makeRange('A',static_cast('Z'+1));
  static const range SmallLetters = makeRange('a',static_cast('z'+1));
  map delimiters;
  delimiters['('] = ')';
  delimiters['['] = ']';
  // clean the formula
  clear();
  for(/*send from above*/;it!=end && *it!=delimiter;/*updated in loop*/){
    // we might have a sub formula
    if(delimiters.count(*it)){
      Formula sub;
      char nextdelim=delimiters[*it];
      sub.parseFromString(++it,end,nextdelim);
      if(!sub.getElementCount()){
        throw FormulaStringParseException() << FormulaString( string(it, end) );
      }
      int count = parseMaybeNumber(++it,end);
      addFormula(sub,count);
      continue;
    }
    string shorthand;
    // Atom names start with a capital letter
    if(!CapitalLetters.isInRange(*it))
      throw FormulaStringParseException() << FormulaString( string(it, end) );
    shorthand+=(*it++);
    // the rest of the name follows
    while(it!=end && SmallLetters.isInRange(*it))
      shorthand+=(*it++);
    int count = parseMaybeNumber(it,end);
    // test if the shorthand exists
    if(!World::getInstance().getPeriode()->FindElement(shorthand))
      throw FormulaStringParseException() << FormulaString( string(it, end) );
    // done, we can get the next one
    addElements(shorthand,count);
  }
  if(it==end && delimiter!=0){
    throw FormulaStringParseException() << FormulaString( string(it, end) );
  }
}
unsigned int Formula::getElementCount() const{
  return numElements;
}
bool Formula::hasElement(const element *element) const{
  ASSERT(element,"Invalid pointer in Formula::hasElement(element*)");
  return hasElement(element->getAtomicNumber());
}
bool Formula::hasElement(atomicNumber_t Z) const{
  ASSERT(Z>0,"Invalid atomic Number");
  ASSERT(World::getInstance().getPeriode()->FindElement(Z),"No Element with this number in Periodentafel");
  return elementCounts.size()>=Z && elementCounts[Z-1];
}
bool Formula::hasElement(const string &shorthand) const{
  const element * element = World::getInstance().getPeriode()->FindElement(shorthand);
  return hasElement(element);
}
void Formula::operator+=(const element *element){
  ASSERT(element,"Invalid pointer in increment of Formula");
  operator+=(element->getAtomicNumber());
}
void Formula::operator+=(atomicNumber_t Z){
  ASSERT(Z>0,"Invalid atomic Number");
  ASSERT(World::getInstance().getPeriode()->FindElement(Z),"No Element with this number in Periodentafel");
  elementCounts.resize(max(Z,elementCounts.size()),0); // No-op when we already have the right size
  // might need to update number of elements
  if(!elementCounts[Z-1]){
    numElements++;
  }
  elementCounts[Z-1]++;    // atomic numbers start at 1
}
void Formula::operator+=(const string &shorthand){
  const element * element = World::getInstance().getPeriode()->FindElement(shorthand);
  operator+=(element);
}
void Formula::operator-=(const element *element){
  ASSERT(element,"Invalid pointer in decrement of Formula");
  operator-=(element->getAtomicNumber());
}
void Formula::operator-=(atomicNumber_t Z){
  ASSERT(Z>0,"Invalid atomic Number");
  ASSERT(World::getInstance().getPeriode()->FindElement(Z),"No Element with this number in Periodentafel");
  ASSERT(elementCounts.size()>=Z && elementCounts[Z-1], "Element not in Formula upon decrement");
  elementCounts[Z-1]--;    // atomic numbers start at 1
  // might need to update number of elements
  if(!elementCounts[Z-1]){
    numElements--;
    // resize the Array if this was at the last position
    if(Z==elementCounts.size()){
      // find the first element from the back that is not equal to zero
      set_t::reverse_iterator riter = find_if(elementCounts.rbegin(),
                                              elementCounts.rend(),
                                              bind1st(not_equal_to(),0));
      // see how many elements are in this range
      set_t::reverse_iterator::difference_type diff = riter - elementCounts.rbegin();
      elementCounts.resize(elementCounts.size()-diff);
    }
  }
}
void Formula::operator-=(const string &shorthand){
  const element * element = World::getInstance().getPeriode()->FindElement(shorthand);
  operator-=(element);
}
void Formula::addElements(const element *element,unsigned int count){
  ASSERT(element,"Invalid pointer in Formula::addElements(element*)");
  addElements(element->getAtomicNumber(),count);
}
void Formula::addElements(atomicNumber_t Z,unsigned int count){
  if(count==0) return;
  ASSERT(Z>0,"Invalid atomic Number");
  ASSERT(World::getInstance().getPeriode()->FindElement(Z),"No Element with this number in Periodentafel");
  elementCounts.resize(max(Z,elementCounts.size()),0); // No-op when we already have the right size
  // might need to update number of elements
  if(!elementCounts[Z-1]){
    numElements++;
  }
  elementCounts[Z-1]+=count;
}
void Formula::addElements(const string &shorthand,unsigned int count){
  const element * element = World::getInstance().getPeriode()->FindElement(shorthand);
  addElements(element,count);
}
void Formula::addFormula(const Formula &formula,unsigned int n){
  for(Formula::const_iterator iter=formula.begin();iter!=formula.end();++iter){
    this->addElements(iter->first,iter->second*n);
  }
}
enumeration Formula::enumerateElements() const{
  enumeration res(1);
  for(Formula::const_iterator iter=begin();iter!=end();++iter){
    res.add(iter->first);
  }
  return res;
}
const unsigned int Formula::operator[](const element *element) const{
  ASSERT(element,"Invalid pointer in access of Formula");
  return operator[](element->getAtomicNumber());
}
const unsigned int Formula::operator[](atomicNumber_t Z) const{
  ASSERT(Z>0,"Invalid atomic Number");
  ASSERT(World::getInstance().getPeriode()->FindElement(Z),"No Element with this number in Periodentafel");
  if(elementCounts.size()FindElement(shorthand);
  return operator[](element);
}
bool Formula::operator==(const Formula &rhs) const{
  // quick check... number of elements used
  if(numElements != rhs.numElements){
    return false;
  }
  // second quick check, size of vectors (== last element in formula)
  if(elementCounts.size()!=rhs.elementCounts.size()){
    return false;
  }
  // slow check: all elements
  // direct access to internal structure means all element-counts have to be compared
  // this avoids access to periodentafel to find elements though and is probably faster
  // in total
  return equal(elementCounts.begin(),
               elementCounts.end(),
               rhs.elementCounts.begin());
}
bool Formula::operator!=(const Formula &rhs) const{
  return !operator==(rhs);
}
Formula::iterator Formula::begin(){
  return iterator(elementCounts,0);
}
Formula::const_iterator Formula::begin() const{
  // this is the only place where this is needed, so this is better than making it mutable
  return const_iterator(const_cast(elementCounts),0);
}
Formula::iterator Formula::end(){
  return iterator(elementCounts);
}
Formula::const_iterator Formula::end() const{
  // this is the only place where this is needed, so this is better than making it mutable
  return const_iterator(const_cast(elementCounts));
}
void Formula::clear(){
  elementCounts.clear();
  numElements = 0;
}
/**************** Iterator structure ********************/
template 
Formula::_iterator::_iterator(set_t &_set) :
    set(&_set)
{
  pos=set->size();
}
template 
Formula::_iterator::_iterator(set_t &_set,size_t _pos) :
  set(&_set),pos(_pos)
{
  ASSERT(pos<=set->size(),"invalid position in iterator construction");
  while(possize() && (*set)[pos]==0) ++pos;
}
template 
Formula::_iterator::_iterator(const _iterator &rhs) :
  set(rhs.set),pos(rhs.pos)
{}
template 
Formula::_iterator::~_iterator(){}
template 
Formula::_iterator&
Formula::_iterator::operator=(const _iterator &rhs){
  set=rhs.set;
  pos=rhs.pos;
  return *this;
}
template 
bool
Formula::_iterator::operator==(const _iterator &rhs){
  return set==rhs.set && pos==rhs.pos;
}
template 
bool
Formula::_iterator::operator!=(const _iterator &rhs){
  return !operator==(rhs);
}
template 
Formula::_iterator
Formula::_iterator::operator++(){
  ASSERT(pos!=set->size(),"Incrementing Formula::iterator beyond end");
  pos++;
  while(possize() && (*set)[pos]==0) ++pos;
  return *this;
}
template 
Formula::_iterator
Formula::_iterator::operator++(int){
  Formula::_iterator retval = *this;
  ++(*this);
  return retval;
}
template 
Formula::_iterator
Formula::_iterator::operator--(){
  ASSERT(pos!=0,"Decrementing Formula::iterator beyond begin");
  pos--;
  while(pos>0 && (*set)[pos]==0) --pos;
  return *this;
}
template 
Formula::_iterator
Formula::_iterator::operator--(int){
  Formula::_iterator retval = *this;
  --(*this);
  return retval;
}
template 
result_type
Formula::_iterator::operator*(){
  const element *element = World::getInstance().getPeriode()->FindElement(pos+1);
  ASSERT(element,"Element with position of iterator not found");
  return make_pair(element,(*set)[pos]);
}
template 
result_type*
Formula::_iterator::operator->(){
  // no one can keep this value around, so a static is ok to avoid temporaries
  static value_type value=make_pair(reinterpret_cast(0),0); // no default constructor for std::pair
  const element *element = World::getInstance().getPeriode()->FindElement(pos+1);
  ASSERT(element,"Element with position of iterator not found");
  value = make_pair(element,(*set)[pos]);
  return &value;
}
// explicit instantiation of all iterator template methods
// this is quite ugly, but there is no better way unless we expose iterator implementation
// instantiate Formula::iterator
template Formula::iterator::_iterator(set_t&);
template Formula::iterator::_iterator(set_t&,size_t);
template Formula::iterator::_iterator(const Formula::iterator&);
template Formula::iterator::~_iterator();
template Formula::iterator &Formula::iterator::operator=(const Formula::iterator&);
template bool Formula::iterator::operator==(const Formula::iterator&);
template bool Formula::iterator::operator!=(const Formula::iterator&);
template Formula::iterator Formula::iterator::operator++();
template Formula::iterator Formula::iterator::operator++(int);
template Formula::iterator Formula::iterator::operator--();
template Formula::iterator Formula::iterator::operator--(int);
template Formula::value_type Formula::iterator::operator*();
template Formula::value_type *Formula::iterator::operator->();
// instantiate Formula::const_iterator
template Formula::const_iterator::_iterator(set_t&);
template Formula::const_iterator::_iterator(set_t&,size_t);
template Formula::const_iterator::_iterator(const Formula::const_iterator&);
template Formula::const_iterator::~_iterator();
template Formula::const_iterator &Formula::const_iterator::operator=(const Formula::const_iterator&);
template bool Formula::const_iterator::operator==(const Formula::const_iterator&);
template bool Formula::const_iterator::operator!=(const Formula::const_iterator&);
template Formula::const_iterator Formula::const_iterator::operator++();
template Formula::Formula::const_iterator Formula::const_iterator::operator++(int);
template Formula::Formula::const_iterator Formula::const_iterator::operator--();
template Formula::Formula::const_iterator Formula::const_iterator::operator--(int);
template const Formula::value_type Formula::const_iterator::operator*();
template const Formula::value_type *Formula::const_iterator::operator->();
/********************** I/O of Formulas ************************************************/
std::ostream &operator<<(std::ostream &ost,const Formula &formula){
  ost << formula.toString();
  return ost;
}