/**********************************************************************
mmcifformat.cpp - Conversion to and from mmCIF format.
Copyright (C) Scarlet Line 2007

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 <openbabel/obmolecformat.h>

#include <iostream>

using namespace std;
namespace OpenBabel
{
  class mmCIFFormat : public OBMoleculeFormat
  {
  public:
    //Register this format type ID
    mmCIFFormat()
    { // Copied from the Chemical MIME Page at http://www.ch.ic.ac.uk/chemime/
      OBConversion::RegisterFormat("mcif", this, "chemical/x-mmcif");
      OBConversion::RegisterFormat("mmcif", this, "chemical/x-mmcif");
      // Uncomment the following line, and this file will handle all CIF formats
      // OBConversion::RegisterFormat("cif", this, "chemical/x-cif");
    }

    virtual const char* Description() //required
    {
      return
        "Macromolecular Crystallographic Information\n ";
        "Read Options e.g. -as\n"
        "  s  Output single bonds only\n"
        "  b  Disable bonding entirely\n\n";
    };

    virtual const char* SpecificationURL()
    { return "http://mmcif.pdb.org/";}; //optional
    // CIF itself is at http://www.iucr.org/iucr-top/cif/index.html

    virtual const char* GetMIMEType() 
    { return "chemical/x-mmcif"; };

    //*** This section identical for most OBMol conversions ***
    ////////////////////////////////////////////////////
    /// The "API" interface functions
  	virtual int SkipObjects(int n, OBConversion* pConv);
    virtual bool ReadMolecule(OBBase* pOb, OBConversion* pConv);
    virtual bool WriteMolecule(OBBase* pOb, OBConversion* pConv);
  };

  //Make an instance of the format class
  mmCIFFormat themmCIFFormat;

  struct CIFTagID
    {
    enum CIFCatName
      {
      unread_CIFCatName,
      atom_site,
      cell,
      chemical,
      chemical_formula,
      symmetry,
      symmetry_equiv,
      space_group,
      MAX_CIFCatName
      };
    enum CIFDataName
      {
      unread_CIFDataName,
      _atom_site_fract_x, // The x coordinate specified as a fraction of _cell_length_a
      _atom_site_fract_y, // The y coordinate specified as a fraction of _cell_length_b
      _atom_site_fract_z, // The z coordinate specified as a fraction of _cell_length_c
      _atom_site_Cartn_x, // The x coordinate in angstroms
      _atom_site_Cartn_y, // The y coordinate in angstroms
      _atom_site_Cartn_z, // The z coordinate in angstroms
      _atom_site_label, // The atomic label if more detailed label info unavailable
      _atom_site_label_atom_id, // The atomic label within the residue
      _atom_site_label_comp_id, // The residue abbreviation, e.g. ILE
      _atom_site_label_entity_id, // The chain number of the residue, e.g. 2
      _atom_site_label_seq_id, // The sequence number of the residue, within the chain, e.g. 12
      _atom_site_type_symbol, // Atomic symbol, e.g. C
      MAX_atom_site,
      _cell_length_a, // Unit-cell length a in Angstroms
      _cell_length_b, // Unit-cell length b in Angstroms
      _cell_length_c, // Unit-cell length c in Angstroms
      _cell_angle_alpha, // Unit-cell angle alpha in degrees
      _cell_angle_beta, // Unit-cell angle beta in degrees
      _cell_angle_gamma, // Unit-cell angle gamma in degrees
      MAX_cell,
      // chemical name organized by increasing desirability
      _chemical_name_common,
      _chemical_name_structure_type,
      _chemical_name_mineral,
      _chemical_name_systematic,
      MAX_chemical,
      // chemical formulae organized by increasing desirability
      _chemical_formula_moiety,
      _chemical_formula_iupac,
      _chemical_formula_structural,
      _chemical_formula_analytical,
      MAX_chemical_formula,
      _symmetry_Int_Tables_number,
      _symmetry_space_group_name_Hall,
      _symmetry_space_group_name_H_M,
      MAX_symmetry,
      _symmetry_equiv_pos_as_xyz,
      MAX_symmetry_equiv,
      _space_group_IT_number,
      _space_group_name_Hall,
      _space_group_name_H_M_alt,
      MAX_space_group,
      MAX_CIFDataName
      };
    char  tagname[76];
    CIFDataName tagid;
    };
  typedef vector<CIFTagID::CIFDataName> CIFColumnList;
  typedef map<string, CIFTagID::CIFDataName> CIFtagmap;
  struct CIFResidueID
    {
    unsigned long ChainNum; // The number of the chain
    unsigned long ResNum;  // The number of the residue within the chain
    CIFResidueID()
      {}
    CIFResidueID(unsigned long c, unsigned long r)
    :ChainNum(c), ResNum(r)
      {}
    CIFResidueID(const CIFResidueID & other)
    :ChainNum(other.ChainNum), ResNum(other.ResNum)
      {}
    CIFResidueID & operator=(const CIFResidueID & other)
      {
      ChainNum = other.ChainNum;
      ResNum = other.ResNum;
      }
    bool operator< (const CIFResidueID & other) const
      {
      return ( ChainNum < other.ChainNum ? true : ( other.ChainNum < ChainNum ? false : ResNum < other.ResNum ) );
      }
    };
  typedef map<CIFResidueID, int> CIFResidueMap;
  CIFtagmap CIFtagLookupTable;

  CIFTagID CIFTagsRead[] =
    {
    { "_atom_site_fract_x", CIFTagID::_atom_site_fract_x },
    { "_atom_site_fract_y", CIFTagID::_atom_site_fract_y },
    { "_atom_site_fract_z", CIFTagID::_atom_site_fract_z },
    { "_atom_site_cartn_x", CIFTagID::_atom_site_Cartn_x },
    { "_atom_site_cartn_y", CIFTagID::_atom_site_Cartn_y },
    { "_atom_site_cartn_z", CIFTagID::_atom_site_Cartn_z },
    { "_atom_site_type_symbol", CIFTagID::_atom_site_type_symbol },
    { "_atom_site_id", CIFTagID::_atom_site_label },
    { "_atom_site_label", CIFTagID::_atom_site_label },
    { "_atom_site_label_atom_id", CIFTagID::_atom_site_label_atom_id },
    { "_atom_site_label_comp_id", CIFTagID::_atom_site_label_comp_id },
    { "_atom_site_label_entity_id", CIFTagID::_atom_site_label_entity_id },
    { "_atom_site_label_seq_id", CIFTagID::_atom_site_label_seq_id },
    { "_cell_length_a", CIFTagID::_cell_length_a },
    { "_cell_length_b", CIFTagID::_cell_length_b },
    { "_cell_length_c", CIFTagID::_cell_length_c },
    { "_cell_angle_alpha", CIFTagID::_cell_angle_alpha },
    { "_cell_angle_beta", CIFTagID::_cell_angle_beta },
    { "_cell_angle_gamma", CIFTagID::_cell_angle_gamma },
    { "_chemical_name_systematic", CIFTagID::_chemical_name_systematic },
    { "_chemical_name_mineral", CIFTagID::_chemical_name_mineral },
    { "_chemical_name_structure_type", CIFTagID::_chemical_name_structure_type },
    { "_chemical_name_common", CIFTagID::_chemical_name_common },
    { "_chemical_formula_analytical", CIFTagID::_chemical_formula_analytical },
    { "_chemical_formula_structural", CIFTagID::_chemical_formula_structural },
    { "_chemical_formula_iupac", CIFTagID::_chemical_formula_iupac },
    { "_chemical_formula_moiety", CIFTagID::_chemical_formula_moiety },
    { "_space_group_it_number", CIFTagID::_space_group_IT_number },
    { "_space_group_name_hall", CIFTagID::_space_group_name_Hall },
    { "_space_group_name_h-m_alt", CIFTagID::_space_group_name_H_M_alt },
    { "_symmetry_int_tables_number", CIFTagID::_symmetry_Int_Tables_number },
    { "_symmetry_space_group_name_hall", CIFTagID::_symmetry_space_group_name_Hall },
    { "_symmetry_space_group_name_h-m", CIFTagID::_symmetry_space_group_name_H_M },
    { "_symmetry_equiv_pos_as_xyz", CIFTagID::_symmetry_equiv_pos_as_xyz },
    { "", CIFTagID::unread_CIFDataName }
    };
  
  class CIFLexer
  {
  public:
    enum TokenType
      {
      UnknownToken,
      KeyDataToken,
      KeyLoopToken,
      KeySaveToken,
      KeySaveEndToken,
      KeyStopToken,
      KeyGlobalToken,
      TagToken,
      ValueToken,
      ValueOrKeyToken,
      MAXTokenType
      };
    struct Token
      {
      TokenType type;
      string as_text;
      double  as_number() const
        { return strtod(as_text.c_str(), 0); }
      unsigned long  as_unsigned() const
        { return strtoul(as_text.c_str(), 0, 10); }
      };
    CIFLexer(istream * in)
    :input(in)
      {
      last_char = 0;
      next_char = input->get();
      }
    bool next_token(CIFLexer::Token & token);
    static CIFTagID::CIFDataName lookup_tag(const string & tag_name);
    static CIFTagID::CIFCatName lookup_cat(CIFTagID::CIFDataName tagid);
    void advance()
      {
      last_char = next_char;
      next_char = input->get();
      }
    void backup(size_t count)
      {
      for ( ++ count; count; -- count )
        input->unget();
      last_char = 0;
      next_char = input->get();
      }
    void backup(size_t count, char next)
      {
      for ( ; count; -- count )
        input->unget();
      last_char = 0;
      next_char = next;
      }
    bool good() const
      { return input->good(); }
  private:
    istream  * input;
    int  last_char, next_char;
  };
  CIFTagID::CIFDataName CIFLexer::lookup_tag(const string & tag_name)
  {
	  if (CIFtagLookupTable.empty())
		  {
		  for (size_t idx = 0; CIFTagsRead[idx].tagid != CIFTagID::unread_CIFDataName; ++ idx)
			  {
			  CIFtagLookupTable.insert(CIFtagmap::value_type(string(CIFTagsRead[idx].tagname), CIFTagsRead[idx].tagid ));
			  }
		  }
  	CIFTagID::CIFDataName rtn = CIFTagID::unread_CIFDataName;
	  CIFtagmap::const_iterator found = CIFtagLookupTable.find(tag_name);
	  if (found != CIFtagLookupTable.end())
		  rtn = (* found).second;
	  return rtn;
  }
  CIFTagID::CIFCatName CIFLexer::lookup_cat(CIFTagID::CIFDataName tagid)
  {
    CIFTagID::CIFCatName catid = CIFTagID::unread_CIFCatName;
    if (tagid > CIFTagID::unread_CIFDataName)
      {
      if (tagid < CIFTagID::MAX_atom_site)
        catid = CIFTagID::atom_site;
      else if (tagid < CIFTagID::MAX_cell)
        catid = CIFTagID::cell;
      else if (tagid < CIFTagID::MAX_chemical)
        catid = CIFTagID::chemical;
      else if (tagid < CIFTagID::MAX_chemical_formula)
        catid = CIFTagID::chemical_formula;
      else if (tagid < CIFTagID::MAX_symmetry)
        catid = CIFTagID::symmetry;
      else if (tagid < CIFTagID::MAX_symmetry_equiv)
        catid = CIFTagID::symmetry_equiv;
      else if (tagid < CIFTagID::MAX_space_group)
        catid = CIFTagID::space_group;
      }
    return catid;
  }
  
  bool CIFLexer::next_token(CIFLexer::Token & token)
  {
  token.type = CIFLexer::UnknownToken;
  token.as_text.clear();
  while (token.type == CIFLexer::UnknownToken && input->good())
    {
    if (next_char <= ' ')
      { // whitespace
      advance();
      }
    else
      { // i.e. not WhiteSpace
      switch(next_char)
        {
      // Comment handling
      case '#':
        do // eat comment to the end of the line
          {
          advance();
          } while (next_char != '\n' && input->good());
        // We are now pointing at EOL or EOF
        break;
      // Tag handling
      case '_':
        do // read name to the next whitespace
          {
          if (next_char == '.') // combines DDL1 and DDL2 tag names
            next_char = '_';
          else
            next_char = tolower(next_char);
          token.as_text.push_back((char)next_char);
          advance();
          } while (next_char > ' ' && input->good());
        // We are now pointing at the next whitespace
        token.type = CIFLexer::TagToken;
        break;
      // Quoted data handling
      case '"':
        do // read name to the next quote-whitespace
          {
          advance();
          if (next_char == '"')
            {
            while (next_char == '"')
              {
              advance();
              if (next_char <= ' ') // whitespace
                break;
              token.as_text.push_back((char)last_char);
              }
            if (next_char <= ' ') // whitespace
              break;
            }
          token.as_text.push_back((char)next_char);
          } while (input->good());
        // We are now pointing at the next whitespace
        token.type = CIFLexer::ValueToken;
        break;
      case '\'':
        do // read name to the next quote-whitespace
          {
          advance();
          if (next_char == '\'')
            {
            while (next_char == '\'')
              {
              advance();
              if (next_char <= ' ') // whitespace
                break;
              token.as_text.push_back((char)last_char);
              }
            if (next_char <= ' ') // whitespace
              break;
            }
          token.as_text.push_back((char)next_char);
          } while (input->good());
        // We are now pointing at the next whitespace
        token.type = CIFLexer::ValueToken;
        break;
      case ';':
        if (last_char == '\n')
          {
          do // read name to the next <eol>-;
            {
            advance();
            if (next_char == '\n')
              {
              while (next_char == '\n')
                {
                advance();
                if (next_char == ';') // end
                  break;
                token.as_text.push_back((char)last_char);
                }
              if (next_char == ';') // end
                {
                advance(); // go past the end
                break;
                }
              }
            token.as_text.push_back((char)next_char);
            } while (input->good());
          // We are now pointing at the next whitespace
          token.type = CIFLexer::ValueToken;
          break;
          }
        // drop through to the default case
      default: // reading an un-quoted text string
        do // read text to the next whitespace
          {
          token.as_text.push_back((char)next_char);
          advance();
          } while (next_char > ' ' && input->good());
        token.type = CIFLexer::ValueOrKeyToken;
        // We are now pointing at the next whitespace
        break;
        }
      }
    }
  if (token.type == CIFLexer::ValueOrKeyToken)
    {
    string::size_type len = token.as_text.size();
    if (len == 1 && token.as_text[0] == '.')
      token.type = CIFLexer::ValueToken;
    else if (!strncasecmp(token.as_text.c_str(), "data_", 5))
      {
      token.type = CIFLexer::KeyDataToken;
      token.as_text.erase(0, 5);
      }
    else if (!strcasecmp(token.as_text.c_str(), "loop_"))
      token.type = CIFLexer::KeyLoopToken;
    else if (!strncasecmp(token.as_text.c_str(), "save_", 5))
      {
      if (len == 5)
        {
        token.type = CIFLexer::KeySaveEndToken;
        }
      else
        {
        token.type = CIFLexer::KeySaveToken;
        token.as_text.erase(0, 5);
        }
      }
    else if (!strcasecmp(token.as_text.c_str(), "stop_"))
      token.type = CIFLexer::KeyStopToken;
    else if (!strcasecmp(token.as_text.c_str(), "global_"))
      token.type = CIFLexer::KeyGlobalToken;
    else
      token.type = CIFLexer::ValueToken;
    }
  return token.type != CIFLexer::UnknownToken;
  }
  /////////////////////////////////////////////////////////////////
 	int mmCIFFormat::SkipObjects(int n, OBConversion* pConv)
  {
    if (n == 0)
      ++ n;
    CIFLexer lexer(pConv->GetInStream());
    CIFLexer::Token token;
    while (n && lexer.good())
      {
      while ( lexer.next_token(token) && token.type != CIFLexer::KeyDataToken);
      -- n;
      }
    if (lexer.good())
      lexer.backup(5 + token.as_text.size(), 'd'); // length of "data_<name>"
      
    return lexer.good() ? 1 : -1;       
  }
  bool mmCIFFormat::ReadMolecule(OBBase* pOb, OBConversion* pConv)
  {
    OBMol* pmol = pOb->CastAndClear<OBMol>();
    if(pmol==NULL)
      return false;

    CIFLexer lexer(pConv->GetInStream());
    CIFLexer::Token token;
    // move to the next data block (i.e. molecule, we hope )
    while (lexer.next_token(token) && token.type != CIFLexer::KeyDataToken);
    if (token.type == CIFLexer::KeyDataToken)
      { // we have found the next data block:
      pmol->BeginModify();
      pmol->SetTitle(token.as_text);
      bool finished = false, token_peeked = false;
      double cell_a = 1.0, cell_b = 1.0, cell_c = 1.0;
      double cell_alpha = 90.0, cell_beta = 90.0, cell_gamma = 90.0;
      CIFTagID::CIFDataName name_tag = CIFTagID::unread_CIFDataName;
      CIFTagID::CIFDataName formula_tag = CIFTagID::unread_CIFDataName;
      int use_cell = 0, use_fract = 0;
      string space_group_name("P1");
      SpaceGroup space_group;
      while (!finished && (token_peeked || lexer.next_token(token)))
        {
        token_peeked = false;
        switch (token.type)
          {
        case CIFLexer::KeyGlobalToken:
          // We have come to the next block:
          if (pmol->NumAtoms() > 0)
            { // Found a molecule, so finished
            finished = true;
            // move back to the start of the global block:
            lexer.backup(token.as_text.size(), 'g'); // length of "global_"
            }
          else // not yet found a molecule, so go to the next data block
            {
            while (lexer.next_token(token) && token.type != CIFLexer::KeyDataToken);
            if (token.type == CIFLexer::KeyDataToken)
              { // we have found the next data block:
              pmol->SetTitle(token.as_text);
              }
            }
          break;
        case CIFLexer::KeyDataToken:
          // We have come to the next data block:
          if (pmol->NumAtoms() > 0)
            { // Found a molecule, so finished
            finished = true;
            // move back to the start of the data block:
            lexer.backup(5 + token.as_text.size(), 'd'); // length of "data_<name>"
            }
          else // not yet found a molecule, so try again
            pmol->SetTitle(token.as_text);
          break;
        case CIFLexer::KeySaveToken:
          { // Simply eat tokens until the save_ ending token
          while (lexer.next_token(token) && token.type != CIFLexer::KeySaveEndToken);
          }
          break;
        case CIFLexer::KeyLoopToken:
          {
          CIFColumnList  columns;
          CIFTagID::CIFCatName catid = CIFTagID::unread_CIFCatName;
          while ( (token_peeked = lexer.next_token(token)) == true && token.type == CIFLexer::TagToken)
            { // Read in the tags
            CIFTagID::CIFDataName tagid = lexer.lookup_tag(token.as_text);
            columns.push_back(tagid);
            if (catid == CIFTagID::unread_CIFCatName && tagid != CIFTagID::unread_CIFDataName)
              catid = lexer.lookup_cat(tagid);
            }
          size_t column_count = columns.size();
          switch (catid)
            {
          case CIFTagID::atom_site:
            {
            int use_cartn = 0, use_residue = 0;
            use_fract = 0;
            CIFTagID::CIFDataName atom_type_tag = CIFTagID::unread_CIFDataName;
            for (CIFColumnList::const_iterator colx = columns.begin(), coly = columns.end(); colx != coly; ++ colx)
              {
              switch (* colx)
                {
              case CIFTagID::_atom_site_Cartn_x:
              case CIFTagID::_atom_site_Cartn_y:
              case CIFTagID::_atom_site_Cartn_z:
                ++ use_cartn;
                break;
              case CIFTagID::_atom_site_fract_x:
              case CIFTagID::_atom_site_fract_y:
              case CIFTagID::_atom_site_fract_z:
                ++ use_fract;
                break;
              case CIFTagID::_atom_site_label_comp_id:
              case CIFTagID::_atom_site_label_seq_id:
                ++ use_residue;
                break;
              case CIFTagID::_atom_site_type_symbol:
              case CIFTagID::_atom_site_label_atom_id:
              case CIFTagID::_atom_site_label:
                if (atom_type_tag < (* colx))
                  atom_type_tag = (* colx);
                break;
                }
              }
            if (use_cartn)
              {
              for (CIFColumnList::iterator colx = columns.begin(), coly = columns.end(); colx != coly; ++ colx)
                if ( (* colx) >= CIFTagID::_atom_site_fract_x && (* colx) <= CIFTagID::_atom_site_fract_z)
                  (* colx) = CIFTagID::unread_CIFDataName;
              use_fract = 0;
              }
            if (use_residue == 2)
              pmol->SetChainsPerceived();
            size_t column_idx = 0;
            OBAtom * atom = 0;
            double x = 0.0, y = 0.0, z = 0.0;
            CIFResidueMap ResidueMap;
            unsigned long chain_num = 1, residue_num = 1;
            string residue_name, atom_label, atom_mol_label;
            while (token.type == CIFLexer::ValueToken) // Read in the Fields
              {
              if (column_idx == 0)
                {
                atom  = pmol->NewAtom();
                x = y = z = 0.0;
                }
              switch (columns[column_idx])
                {
              case CIFTagID::_atom_site_label: // The atomic label within the molecule
                atom_mol_label.assign(token.as_text);
                if (atom_type_tag != CIFTagID::_atom_site_label)
                  break;
                // else drop through to _atom_site_type_symbol
              case CIFTagID::_atom_site_type_symbol:
                atom->SetAtomicNum(etab.GetAtomicNum(token.as_text.c_str()));
                atom->SetType(token.as_text);
                break;
              case CIFTagID::_atom_site_fract_x:
              case CIFTagID::_atom_site_Cartn_x:
                x = token.as_number();
                break;
              case CIFTagID::_atom_site_fract_y:
              case CIFTagID::_atom_site_Cartn_y:
                y = token.as_number();
                break;
              case CIFTagID::_atom_site_fract_z:
              case CIFTagID::_atom_site_Cartn_z:
                z = token.as_number();
                break;
              case CIFTagID::_atom_site_label_atom_id: // The atomic label within the residue
                atom_label.assign(token.as_text);
                if (atom_type_tag == CIFTagID::_atom_site_label_atom_id)
                  {
                  for (string::iterator posx = token.as_text.begin(), posy = token.as_text.end(); posx != posy; ++ posx)
                    {
                    char c = (char)toupper(* posx);
                    if ( c < 'A' || c > 'Z' )
                      {
                      token.as_text.erase(posx, posy);
                      break;
                      }
                    }
                  atom->SetAtomicNum(etab.GetAtomicNum(token.as_text.c_str()));
                  atom->SetType(token.as_text);
                  }
                break;
              case CIFTagID::_atom_site_label_comp_id: // The residue abbreviation, e.g. ILE
                residue_name.assign(token.as_text);
                break;
              case CIFTagID::_atom_site_label_entity_id: // The chain number of the residue, e.g. 2
                chain_num = token.as_unsigned();
                break;
              case CIFTagID::_atom_site_label_seq_id: // The sequence number of the residue, within the chain, e.g. 12
                residue_num = token.as_unsigned();
                break;
              case CIFTagID::unread_CIFDataName:
              default:
                break;
                }
              ++ column_idx;
              if (column_idx == column_count)
                {
                atom->SetVector(x, y, z);
                if (use_residue == 2)
                  {
                  CIFResidueID res_id(chain_num, residue_num);
                  CIFResidueMap::const_iterator resx = ResidueMap.find(res_id);
                  OBResidue * res;
                  if (resx == ResidueMap.end())
                    {
                    ResidueMap[res_id] = pmol->NumResidues();
                    res  = pmol->NewResidue();
                    res->SetChainNum(chain_num);
                    res->SetNum(residue_num);
                    res->SetName(residue_name);
                    }
                  else
                    res = pmol->GetResidue( (* resx).second );
                  res->AddAtom(atom);
                  if (!atom_label.empty())
                    res->SetAtomID(atom, atom_label);
                  unsigned long serial_no = strtoul(atom_mol_label.c_str(), 0, 10);
                  if (serial_no > 0)
                    res->SetSerialNum(atom, serial_no);
                  }
                column_idx = 0;
                }
              token_peeked = lexer.next_token(token);
              }
            }
            break;
          case CIFTagID::symmetry_equiv:
            {
            size_t column_idx = 0;
            while (token.type == CIFLexer::ValueToken) // Read in the Fields
              {
              if (columns[column_idx] == CIFTagID::_symmetry_equiv_pos_as_xyz)
                space_group.AddTransform(token.as_text);
              ++ column_idx;
              if (column_idx == column_count)
                column_idx = 0;
              token_peeked = lexer.next_token(token);
              }
            }
            break;
          case CIFTagID::unread_CIFCatName:
          default:
            while (token.type == CIFLexer::ValueToken) // Eat the values, we don't want them
              token_peeked = lexer.next_token(token);
            break;
            }
          }
          break;
        case CIFLexer::TagToken:
          {
          CIFTagID::CIFDataName tag_id = lexer.lookup_tag(token.as_text);
          // get the value
          lexer.next_token(token);
          switch (tag_id)
            {
          case CIFTagID::_cell_length_a:
            cell_a = token.as_number();
            ++ use_cell;
            break;
          case CIFTagID::_cell_length_b:
            cell_b = token.as_number();
            ++ use_cell;
            break;
          case CIFTagID::_cell_length_c:
            cell_c = token.as_number();
            ++ use_cell;
            break;
          case CIFTagID::_cell_angle_alpha:
            cell_alpha = token.as_number();
            ++ use_cell;
            break;
          case CIFTagID::_cell_angle_beta:
            cell_beta = token.as_number();
            ++ use_cell;
            break;
          case CIFTagID::_cell_angle_gamma:
            cell_gamma = token.as_number();
            ++ use_cell;
            break;
          case CIFTagID::_chemical_name_systematic:
          case CIFTagID::_chemical_name_mineral:
          case CIFTagID::_chemical_name_structure_type:
          case CIFTagID::_chemical_name_common:
            if (tag_id > name_tag)
              {
              name_tag = tag_id;
              pmol->SetTitle(token.as_text);
              }
            break;
          case CIFTagID::_chemical_formula_analytical:
          case CIFTagID::_chemical_formula_structural:
          case CIFTagID::_chemical_formula_iupac:
          case CIFTagID::_chemical_formula_moiety:
            if (tag_id > formula_tag)
              {
              formula_tag = tag_id;
              pmol->SetFormula(token.as_text);
              }
            break;
          case CIFTagID::_space_group_IT_number:
          case CIFTagID::_symmetry_Int_Tables_number:
            space_group_name.assign(token.as_text);
            space_group.SetId(atoi(space_group_name.c_str()));
            break;
          case CIFTagID::_space_group_name_Hall:
          case CIFTagID::_symmetry_space_group_name_Hall:
            space_group_name.assign(token.as_text);
            space_group.SetHallName(space_group_name);
            break;
          case CIFTagID::_space_group_name_H_M_alt:
          case CIFTagID::_symmetry_space_group_name_H_M:
            space_group_name.assign(token.as_text);
            space_group.SetHMName(space_group_name);
            break;
          case CIFTagID::_symmetry_equiv_pos_as_xyz:
            space_group.AddTransform(token.as_text);
            break;
          default: // eat the value for this tag
            break;
            }
          }
          break;
        case CIFLexer::KeyStopToken:
        case CIFLexer::ValueToken:
        default:
          break;
          }
        }
      if (pmol->NumAtoms() > 0)
        {
        if (use_cell >= 6)
          {
          OBUnitCell * pCell = new OBUnitCell;
          pCell->SetOrigin(fileformatInput);
          pCell->SetData(cell_a, cell_b, cell_c,
                         cell_alpha,
                         cell_beta,
                         cell_gamma
                         );
          pCell->SetSpaceGroup(space_group_name);
          const SpaceGroup * pSpaceGroup = SpaceGroup::Find( & space_group);
          if (pSpaceGroup)
            pCell->SetSpaceGroup(pSpaceGroup);
          pmol->SetData(pCell);
          if (use_fract)
            {
            for (OBAtomIterator atom_x = pmol->BeginAtoms(), atom_y = pmol->EndAtoms(); atom_x != atom_y; ++ atom_x)
              {
              OBAtom * atom = (* atom_x);
              atom->SetVector(atom->GetX() * cell_a, atom->GetY() * cell_b, atom->GetZ() * cell_c);
              }
            }
          }
        if (!pConv->IsOption("b",OBConversion::INOPTIONS))
          {
          pmol->ConnectTheDots();
          if (!pConv->IsOption("s",OBConversion::INOPTIONS))
            pmol->PerceiveBondOrders();
          }
        }
  	  pmol->EndModify();
      }

    return (pmol->NumAtoms() > 0 ? true : false);
  }

  ////////////////////////////////////////////////////////////////

  bool mmCIFFormat::WriteMolecule(OBBase* pOb, OBConversion* pConv)
  {
    OBMol* pmol = dynamic_cast<OBMol*>(pOb);
    if(pmol==NULL)
      return false;

    //Define some references so we can use the old parameter names
    ostream & ofs = * pConv->GetOutStream();

    char buffer[BUFF_SIZE];
    
    string id;
    for (const char * p = pmol->GetTitle(); * p; ++ p)
      if ( (* p) > ' ' && (* p) <= '~' )
        id.append(1, (char)toupper(* p));
    if (id.empty())
      {
      snprintf(buffer, BUFF_SIZE, "T%d", time(0));
      id.assign(buffer);
      }
    ofs << "# --------------------------------------------------------------------------" << endl;
    ofs << "#" << endl;
    ofs << "# CIF file generated by openbabel " << BABEL_VERSION << " http://openbabel.sourceforge.net/" << endl;
    ofs << "# to comply with the Macromolecular CIF Dictionary  (cif_mm.dic) version  2.0.11 http://mmcif.pdb.org/" << endl;
    ofs << "# The contents of this file were derived from " << pConv->GetInFilename() << endl;
    ofs << "#" << endl;
    ofs << "#---------------------------------------------------------------------------" << endl;
    ofs << endl;
    ofs << "data_" << id << endl;
    ofs << endl;
    ofs << "###########" << endl;
    ofs << "## ENTRY ##" << endl;
    ofs << "###########" << endl;
    ofs << endl;
    ofs << "_entry.id\t" << id << endl;
    ofs << endl;
    if (* (pmol->GetTitle()))
      {
      ofs << "##############" << endl;
      ofs << "## CHEMICAL ##" << endl;
      ofs << "##############" << endl;
      ofs << endl;
      ofs << "_chemical.entry_id" << id << endl;
      ofs << "_chemical.name_common\t'" << pmol->GetTitle() << "'" << endl;
      ofs << endl;
      }
    if (! (pmol->GetSpacedFormula().empty()))
      {
      ofs << "######################" << endl;
      ofs << "## CHEMICAL FORMULA ##" << endl;
      ofs << "######################" << endl;
      ofs << endl;
      ofs << "_chemical_formula.entry_id" << id << endl;
      ofs << "_chemical_formula.structural\t'" << pmol->GetFormula() << "'" << endl;
      ofs << endl;
      }
    ofs << "###############" << endl;
    ofs << "## ATOM_SITE ##" << endl;
    ofs << "###############" << endl;                        
    ofs << endl;
    ofs << "loop_" << endl;
    ofs << "_atom_site.id" << endl;
    ofs << "_atom_site.type_symbol" << endl;
    bool has_residues = (pmol->NumResidues() > 0);
    if (has_residues)
      {
      ofs << "_atom_site.label_atom_id" << endl;                  
      ofs << "_atom_site.label_comp_id" << endl;                  
      ofs << "_atom_site.label_entity_id" << endl;                  
      ofs << "_atom_site.label_seq_id" << endl;
      }                  
    ofs << "_atom_site.Cartn_x" << endl;                        
    ofs << "_atom_site.Cartn_y" << endl;                        
    ofs << "_atom_site.Cartn_z" << endl;
    size_t site_id = 1;                        
    for (OBAtomIterator atom_x = pmol->BeginAtoms(), atom_y = pmol->EndAtoms(); atom_x != atom_y; ++ atom_x, ++ site_id)
      {
      OBAtom * atom = (* atom_x);
      ofs << '\t' << site_id << '\t' << etab.GetSymbol(atom->GetAtomicNum());
      if (has_residues)
        {
        OBResidue * pRes = atom->GetResidue();
        string resname(pRes->GetName()), atomname(pRes->GetAtomID(atom));
        if (atomname.empty())
          {
          snprintf(buffer, BUFF_SIZE, "%s%u", etab.GetSymbol(atom->GetAtomicNum()), site_id);
          atomname.assign(buffer);
          }
        if (resname.empty())
          resname.assign("UNK");
        ofs << '\t' << atomname << '\t' << resname << '\t' << pRes->GetChainNum() << '\t' << pRes->GetNum() << endl;
        }
      ofs << '\t' << atom->GetX() << '\t' << atom->GetY() << '\t' << atom->GetZ() << endl;
      }
    ofs << endl;
    if (pmol->HasData(OBGenericDataType::UnitCell))
      {
      OBUnitCell * pCell = (OBUnitCell * )pmol->GetData(OBGenericDataType::UnitCell);
      ofs << "##########" << endl;
      ofs << "## CELL ##" << endl;
      ofs << "##########" << endl;
      ofs << endl;
      ofs << "_cell.entry_id" << id << endl;
      ofs << "_cell.length_a\t" << pCell->GetA() << endl;
      ofs << "_cell.length_b\t" << pCell->GetB() << endl;
      ofs << "_cell.length_c\t" << pCell->GetC() << endl;
      ofs << "_cell.angle_alpha\t" << pCell->GetAlpha() << endl;                 
      ofs << "_cell.angle_beta\t"  << pCell->GetBeta() << endl;                 
      ofs << "_cell.angle_gamma\t" << pCell->GetGamma() << endl;                
      ofs << endl;
      const SpaceGroup * pSG = pCell->GetSpaceGroup();
      if (pSG)
        {
        ofs << "#################" << endl;
        ofs << "## SPACE GROUP ##" << endl;
        ofs << "#################" << endl;
        ofs << endl;
        ofs << "_space_group.id" << id << endl;
        bool loop_transforms = false;
        if (pSG->GetId())
          {
          ofs << "_space_group.IT_number\t" << pSG->GetId() << endl;
          }
        if (! (pSG->GetHallName().empty()))
          {
          loop_transforms = true;
          ofs << "_space_group.name_Hall\t'" << pSG->GetHallName() << "'" << endl;
          }
        if (! (pSG->GetHMName().empty()))
          {
          loop_transforms = true;
          ofs << "_space_group.name_H-M_alt\t'" << pSG->GetHMName() << "'" << endl;
          }
        ofs << endl;
        
        transform3dIterator transx;
        const transform3d * trans = pSG->BeginTransform(transx);
        if (trans)
          {
          ofs << "####################" << endl;
          ofs << "## SYMMETRY EQUIV ##" << endl;
          ofs << "####################" << endl;
          ofs << endl;
          size_t symid = 1;
          if (!loop_transforms)
            {
            ofs << "_symmetry_equiv.id" << symid << endl;
            ofs << "_symmetry_equiv.pos_as_xyz\t'" << trans->DescribeAsString() << "'" << endl;
            }
          else
            {
            ofs << "loop_" << endl;
            ofs << "_symmetry_equiv.id" << endl;
            ofs << "_symmetry_equiv.pos_as_xyz" << endl;
            while (trans)
              {
              ofs << '\t' << symid << "\t'" << trans->DescribeAsString() << "'" << endl;
              trans = pSG->NextTransform(transx);
              }
            }
          ofs << endl;
          }
        }
      }

    return true;
  }

} //namespace OpenBabel
