/**********************************************************************
Copyright (C) 2007 by Chris Morley
 
This file is part of the Open Babel project.
For more information, see <http://openbabel.sourceforge.net/>
 
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 version 2 of the License.
 
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.
***********************************************************************/
#include "openbabel/babelconfig.h"
#include <string>
#include <iomanip>
#include "openbabel/mol.h"
#include "openbabel/obconversion.h"
#include "openbabel/reaction.h"

using namespace std;
//using std::tr1::shared_ptr;

namespace OpenBabel
{
  class SmiReactFormat : public OBFormat
  {
  public:
    //Register this format type ID
    SmiReactFormat()
    {
      OBConversion::RegisterFormat("rsmi",this);
    }

    virtual const char* Description()
    {
      return
        "Reaction SMILES format\n"
        "Write Options e.g. -xt\n"
        "  r radicals lower case eg ethyl is Cc\n"
        "\n";
 
    }

    virtual const char* GetMIMEType() 
    { return "chemical/x-daylight-smiles"; }; // not right, need something else

    virtual const char* TargetClassDescription()
    {
      return OBReaction::ClassDescription();
    }

    const type_info& GetType()
    {
      return typeid(OBReaction*);
    }


    ////////////////////////////////////////////////////
    /// The "API" interface functions
    virtual bool ReadMolecule(OBBase* pReact, OBConversion* pConv);
    virtual bool WriteMolecule(OBBase* pReact, OBConversion* pConv);

    ////////////////////////////////////////////////////
    /// The "Convert" interface functions
    virtual bool ReadChemObject(OBConversion* pConv)
    {
      //Makes a new OBReaction and new associated OBMols
      OBReaction* pReact = new OBReaction;
      bool ret=ReadMolecule(pReact,pConv); //call the "API" read function

      std::string auditMsg = "OpenBabel::Read reaction ";
      std::string description(Description());
      auditMsg += description.substr(0,description.find('\n'));
      obErrorLog.ThrowError(__FUNCTION__,
                            auditMsg,
                            obAuditMsg);

      if(ret) //Do transformation and return molecule
        return pConv->AddChemObject(pReact)!=0;
      else
        pConv->AddChemObject(NULL);
      return false;
    }

    virtual bool WriteChemObject(OBConversion* pConv)
    {
      //WriteChemObject() always deletes the object retrieved by GetChemObject
      OBBase* pOb = pConv->GetChemObject();
      OBReaction* pReact = dynamic_cast<OBReaction*>(pOb);
      if(pReact==NULL)
        return false;

      bool ret=false;
      ret=WriteMolecule(pReact,pConv);

      std::string auditMsg = "OpenBabel::Write reaction ";
      std::string description(Description());
      auditMsg += description.substr( 0, description.find('\n') );
      obErrorLog.ThrowError(__FUNCTION__,
                            auditMsg,
                            obAuditMsg);
      delete pOb;
      return ret;
    }

  };

  //Make an instance of the format class
  SmiReactFormat theSmiReactFormat;

  /////////////////////////////////////////////////////////////////
  bool SmiReactFormat::ReadMolecule(OBBase* pOb, OBConversion* pConv)
  {
    //It's really a reaction, not a molecule.
    //Doesn't make a new OBReaction object, but does make mew reactant and product OBMols
    OBReaction* pReact = pOb->CastAndClear<OBReaction>();

    istream &ifs = *pConv->GetInStream();
    OBConversion sconv; //Copy
    if(!sconv.SetInFormat("smi"))
      {
        obErrorLog.ThrowError(__FUNCTION__, "Smiles format needed but not found", obError);
        return false;
      }

    string ln, rsmiles, title, s;
    string::size_type pos, pos2;

    //Ignore lines that start with # or /
    while(ifs && ifs.peek()=='#' || ifs.peek()=='/')
      if(!getline(ifs, ln))
        return false;

    //Get title
    if(getline(ifs, ln))
      {
        pos = ln.find_first_of(" \t");
        if(pos!=string::npos)
          {
            rsmiles = ln.substr(0,pos);
            title = ln.substr(pos+1);
            Trim(title);
            pReact->SetTitle(title);
          }
        else
          rsmiles = ln;
      }
    //Check for illegal characters
    pos= rsmiles.find_first_of(",<\"\'!^&_|{}");
    if(pos!=string::npos)
      {
        obErrorLog.ThrowError(__FUNCTION__, 
                              rsmiles + " contained a character '" + rsmiles[pos] + "' which is invalid in SMILES", obError);
        return false;
      }
  
    pos = rsmiles.find('>');
    if(pos==string::npos)
      {
        obErrorLog.ThrowError(__FUNCTION__, "No > in reaction", obError);
        return false;
      }
  
    vector<OBMol> mols;
    vector<OBMol>::iterator itr;

    //Extract reactants and split into individual molecules
    OBMol jreactants;
    s = rsmiles.substr(0,pos); 
    if(!sconv.ReadString(&jreactants, s)) 
      {
        obErrorLog.ThrowError(__FUNCTION__, "Cannot read reactant", obError);
        return false;
      }
    mols = jreactants.Separate();
    for(itr=mols.begin();itr!=mols.end();++itr)
      pReact->AddReactant(shared_ptr<OBMol>(new OBMol(*itr)));

    pos2 = rsmiles.find('>', pos+1);
    if(pos2==string::npos)
      {
        obErrorLog.ThrowError(__FUNCTION__, "Only one > in reaction", obError);
        return false;
      }
  
    //Extract agent (not split into separate molecules)
    if(pos2-pos>1)
      {
        OBMol* pAgent = new OBMol;
        s = rsmiles.substr(pos+1,pos2-pos-1);
        if(!sconv.ReadString(pAgent, s)) 
          {
            obErrorLog.ThrowError(__FUNCTION__, "Cannot read agent", obError);
            delete pAgent;
            return false;
          }
        pReact->AddAgent(shared_ptr<OBMol>(pAgent));
      }

    //Extract products and split into separate molecules
    OBMol jproducts;
    s = rsmiles.substr(pos2+1); 
    if(!sconv.ReadString(&jproducts, s)) 
      {
        obErrorLog.ThrowError(__FUNCTION__, "Cannot read product", obError);
        return false;
      }
    mols.clear();
    mols = jproducts.Separate();
    for(itr=mols.begin();itr!=mols.end();++itr)
      pReact->AddProduct(shared_ptr<OBMol>(new OBMol(*itr)));

    return true;
  }


  /////////////////////////////////////////////////////////////////
  bool SmiReactFormat::WriteMolecule(OBBase* pOb, OBConversion* pConv)
  {
    //It's really a reaction, not a molecule.
    //Cast output object to the class type need, i.e. OBReaction
    OBReaction* pReact = dynamic_cast<OBReaction*>(pOb);
    if(pReact==NULL)
      return false;
    ostream &ofs = *pConv->GetOutStream();


    OBFormat* pSmiFormat = OBConversion::FindFormat("SMI");
    if(!pSmiFormat)
      return false;
    pConv->AddOption("smilesonly",OBConversion::OUTOPTIONS);//supresses title and new line
    pConv->AddOption("c",OBConversion::OUTOPTIONS);//output atom classes if available

    OBMol jReactants;
    for(int i=0;i<pReact->NumReactants();++i)
      jReactants += *(pReact->GetReactant(i));
    
    if(!pSmiFormat->WriteMolecule(&jReactants, pConv))
      return false;

    ofs << '>';

    shared_ptr<OBMol> spAgent = pReact->GetAgent();
    if(spAgent.get())
      if(!pSmiFormat->WriteMolecule(spAgent.get(), pConv))
        return false;

    ofs << '>';

    OBMol jProducts;
    for(int i=0;i<pReact->NumProducts();++i)
      jProducts += *(pReact->GetProduct(i));
    
    if(!pSmiFormat->WriteMolecule(&jProducts, pConv))
      return false;

    if(!pReact->GetComment().empty())
      ofs << '\t' << pReact->GetComment();

    ofs << endl;

    return true;
  }

} //namespace
