//
//  Copyright (C) 2001-2014 Greg Landrum and Rational Discovery LLC
//
//   @@ All Rights Reserved @@
//  This file is part of the RDKit.
//  The contents are covered by the terms of the BSD license
//  which is included in the file license.txt, found at the root
//  of the RDKit source tree.
//
#ifndef _RD_BOND_H
#define _RD_BOND_H

// std stuff
#include <iostream>

// Ours
// FIX: grn...
#include <Query/QueryObjects.h>
#include <RDGeneral/types.h>
#include <GraphMol/details.h>
#include <boost/foreach.hpp>

namespace RDKit{
  class ROMol;
  class RWMol;
  class Atom;
  typedef boost::shared_ptr<Atom>    ATOM_SPTR;

  //! class for representing a bond
  /*!

    <b>Notes:</b>
      - many of the methods of Atom require that the Atom be associated
        with a molecule (an ROMol).
      - each Bond maintains a Dict of \c properties:  
          - Each \c property is keyed by name and can store an
	    arbitrary type.
	  - \c Properties can be marked as \c calculated, in which case
	    they will be cleared when the \c clearComputedProps() method
	    is called.
          - Because they have no impact upon chemistry, all \c property
	    operations are \c const, this allows extra flexibility for
	    clients who need to store extra data on Bond objects.

  */
  class Bond {
    friend class RWMol;
    friend class ROMol;
  public:
    typedef boost::shared_ptr<Bond>    BOND_SPTR;
    // FIX: grn...
    typedef Queries::Query<int,Bond const *,true> QUERYBOND_QUERY;

    //! the type of Bond
    typedef enum {
      UNSPECIFIED = 0,
      SINGLE,
      DOUBLE,
      TRIPLE,
      QUADRUPLE,
      QUINTUPLE,
      HEXTUPLE,
      ONEANDAHALF,
      TWOANDAHALF,
      THREEANDAHALF,
      FOURANDAHALF,
      FIVEANDAHALF,
      AROMATIC,
      IONIC,
      HYDROGEN,
      THREECENTER,
      DATIVEONE,   //!< one-electron dative (e.g. from a C in a Cp ring to a metal)
      DATIVE,      //!< standard two-electron dative
      DATIVEL,     //!< standard two-electron dative
      DATIVER,     //!< standard two-electron dative
      OTHER,       
      ZERO         //!< Zero-order bond (from http://pubs.acs.org/doi/abs/10.1021/ci200488k)
    } BondType;

    //! the bond's direction (for chirality)
    typedef enum {   
      NONE=0,         //!< no special style
      BEGINWEDGE,     //!< wedged: narrow at begin
      BEGINDASH,      //!< dashed: narrow at begin
      // FIX: this may not really be adequate
      ENDDOWNRIGHT,   //!< for cis/trans
      ENDUPRIGHT,     //!<  ditto
      EITHERDOUBLE,   //!< a "crossed" double bond 
      UNKNOWN,        //!< intentionally unspecified stereochemistry
    } BondDir;
    
    //! the nature of the bond's stereochem (for cis/trans)
    typedef enum {   // stereochemistry of double bonds
      STEREONONE=0,  // no special style
      STEREOANY,     // intentionally unspecified
      // -- Put any true specifications about this point so
      // that we can do comparisons like if(bond->getStereo()>Bond::STEREOANY) 
      STEREOZ,       // Z double bond
      STEREOE,       // E double bond
    } BondStereo;
    
    Bond();
    //! construct with a particular BondType
    explicit Bond(BondType bT);
    Bond(const Bond &other);
    virtual ~Bond();
    Bond &operator=(const Bond &other);

    //! returns a copy
    /*!
      <b>Note:</b> the caller is responsible for <tt>delete</tt>ing
       the returned pointer.
    */
    virtual Bond *copy() const;

    //! returns our \c bondType
    BondType getBondType() const { return static_cast<BondType>(d_bondType); };
    //! sets our \c bondType
    void setBondType(BondType bT) { d_bondType = bT; };
    //! \brief returns our \c bondType as a double
    //!   (e.g. SINGLE->1.0, AROMATIC->1.5, etc.)
    double getBondTypeAsDouble() const;

    //! returns our contribution to the explicit valence of an Atom
    /*!
      <b>Notes:</b>
        - requires an owning molecule
    */
    double getValenceContrib(const Atom *at) const;
    // \overload
    double getValenceContrib(ATOM_SPTR at) const;

    //! sets our \c isAromatic flag
    void setIsAromatic( bool what ) { df_isAromatic = what; };
    //! returns the status of our \c isAromatic flag
    bool getIsAromatic() const { return df_isAromatic; };

    //! sets our \c isConjugated flag
    void setIsConjugated(bool what) {df_isConjugated = what;};
    //! returns the status of our \c isConjugated flag
    bool getIsConjugated() const {return df_isConjugated;};
  
    //! returns a reference to the ROMol that owns this Bond
    ROMol &getOwningMol() const { return *dp_mol; };
    //! sets our owning molecule
    void setOwningMol(ROMol *other);
    //! sets our owning molecule
    void setOwningMol(ROMol &other) {setOwningMol(&other);};

    //! returns our index within the ROMol
    /*!
      <b>Notes:</b>
        - this makes no sense if we do not have an owning molecule

    */
    unsigned int getIdx() const {return d_index;};
    //! sets our index within the ROMol
    /*!
      <b>Notes:</b>
        - this makes no sense if we do not have an owning molecule
	- the index should be <tt>< this->getOwningMol()->getNumBonds()</tt>
    */
    void setIdx(unsigned int index) {d_index=index;};

    //! returns the index of our begin Atom
    /*!
      <b>Notes:</b>
        - this makes no sense if we do not have an owning molecule
    */
    unsigned int getBeginAtomIdx() const { return d_beginAtomIdx; };

    //! returns the index of our end Atom
    /*!
      <b>Notes:</b>
        - this makes no sense if we do not have an owning molecule
    */
    unsigned int getEndAtomIdx() const { return d_endAtomIdx; };

    //! given the index of one Atom, returns the index of the other
    /*!
      <b>Notes:</b>
        - this makes no sense if we do not have an owning molecule
    */
    unsigned int getOtherAtomIdx(unsigned int thisIdx) const;

    //! sets the index of our begin Atom
    /*!
      <b>Notes:</b>
        - requires an owning molecule
    */
    void setBeginAtomIdx(unsigned int what);
    //! sets the index of our end Atom
    /*!
      <b>Notes:</b>
        - requires an owning molecule
    */
    void setEndAtomIdx(unsigned int what);

    //! sets our begin Atom
    /*!
      <b>Notes:</b>
        - requires an owning molecule
    */
    void setBeginAtom(Atom *at);
    //! \overload
    void setBeginAtom(ATOM_SPTR at);
    //! sets our end Atom
    /*!
      <b>Notes:</b>
        - requires an owning molecule
    */
    void setEndAtom(Atom *at);
    //! \overload
    void setEndAtom(ATOM_SPTR at);


    //! returns a pointer to our begin Atom
    /*!
      <b>Notes:</b>
        - requires an owning molecule
    */
    Atom *getBeginAtom() const;
    //! returns a pointer to our end Atom
    /*!
      <b>Notes:</b>
        - requires an owning molecule
    */
    Atom *getEndAtom() const;
    //! returns a pointer to the other Atom
    /*!
      <b>Notes:</b>
        - requires an owning molecule
    */
    Atom *getOtherAtom(Atom const *what) const;

    // ------------------------------------
    // Please see the note in Atom.h for some explanation
    // of these methods
    // ------------------------------------
  
    // This method can be used to distinguish query bonds from standard bonds
    virtual bool hasQuery() const { return false; };

    // FIX: the const crap here is all mucked up.
    //! NOT CALLABLE
    virtual void setQuery(QUERYBOND_QUERY *what);
    //! NOT CALLABLE
    virtual QUERYBOND_QUERY *getQuery() const;

    //! NOT CALLABLE
    virtual void expandQuery(QUERYBOND_QUERY *what,
                             Queries::CompositeQueryType how=Queries::COMPOSITE_AND,
                             bool maintainOrder=true);

    //! returns whether or not we match the argument
    /*!
        <b>Notes:</b>
	  - for Bond objects, "match" means that either one of the Bonds
	    has \c bondType Bond::UNSPECIFIED or both Bonds have the
	    same \c bondType.
    */
    virtual bool Match(Bond const *what) const;
    //! \overload
    virtual bool Match(const Bond::BOND_SPTR what) const;
  
    //! sets our direction
    void setBondDir(BondDir what) { d_dirTag = what; };
    //! returns our direction
    BondDir getBondDir() const { return static_cast<BondDir>(d_dirTag); };
  
    //! sets our stereo code
    void setStereo(BondStereo what) { d_stereo = what; };
    //! returns our stereo code
    BondStereo getStereo() const { return static_cast<BondStereo>(d_stereo); };

    //! returns the indices of our stereo atoms
    const INT_VECT &getStereoAtoms() const {
      if(!dp_stereoAtoms){
        const_cast<Bond *>(this)->dp_stereoAtoms = new INT_VECT();
      }
      return *dp_stereoAtoms;
    };
    //! \overload
    INT_VECT &getStereoAtoms() {
      if(!dp_stereoAtoms) dp_stereoAtoms = new INT_VECT();
      return *dp_stereoAtoms;
    };
  
    // ------------------------------------
    //  Local Property Dict functionality
    //  FIX: at some point this stuff should go in a mixin class
    // ------------------------------------
    //! returns a list with the names of our \c properties
    STR_VECT getPropList() const {
      return dp_props->keys();
    }

    //! sets a \c property value
    /*!
       \param key the name under which the \c property should be stored.
           If a \c property is already stored under this name, it will be
	   replaced.
       \param val the value to be stored
       \param computed (optional) allows the \c property to be flagged
           \c computed.
     */
    template <typename T>
    void setProp(const char *key,T val, bool computed=false) const{
      //if(!dp_props) dp_props = new Dict();
      std::string what(key);
      setProp(what,val, computed);
    }
    //! \overload
    template <typename T>
    void setProp(const std::string &key,T val, bool computed=false ) const{
      //setProp(key.c_str(),val);
      if (computed) {
          STR_VECT compLst;
          getPropIfPresent(detail::computedPropName, compLst);
          if (std::find(compLst.begin(), compLst.end(), key) == compLst.end()) {
              compLst.push_back(key);
              dp_props->setVal(detail::computedPropName, compLst);
          }
      }
      dp_props->setVal(key,val);
    }

    //! allows retrieval of a particular property value
    /*!

       \param key the name under which the \c property should be stored.
           If a \c property is already stored under this name, it will be
	   replaced.
       \param res a reference to the storage location for the value.

       <b>Notes:</b>
         - if no \c property with name \c key exists, a KeyErrorException will be thrown.
	 - the \c boost::lexical_cast machinery is used to attempt type conversions.
	   If this fails, a \c boost::bad_lexical_cast exception will be thrown.

    */
    template <typename T>
    void getProp(const char *key,T &res) const {
      PRECONDITION(dp_props,"getProp called on empty property dict");
      dp_props->getVal(key,res);
    }
    //! \overload
    template <typename T>
    void getProp(const std::string &key,T &res) const {
      PRECONDITION(dp_props,"getProp called on empty property dict");
      dp_props->getVal(key,res);
    }

    //! \Overload
    template <typename T>
    T getProp(const char *key) const {
      return dp_props->getVal<T>(key);
    }
    //! \overload
    template <typename T>
    T getProp(const std::string &key) const {
      return dp_props->getVal<T>(key);
    }

    //! returns whether or not we have a \c property with name \c key
    //!  and assigns the value if we do

    template <typename T>
    bool getPropIfPresent(const char *key,T &res) const {
        return dp_props->getValIfPresent(key,res);
    }
    //! \overload
    template <typename T>
    bool getPropIfPresent(const std::string &key,T &res) const {
        return dp_props->getValIfPresent(key,res);
    }

    //! returns whether or not we have a \c property with name \c key
    bool hasProp(const char *key) const {
      if(!dp_props) return false;
      return dp_props->hasVal(key);
    };
    //! \overload
    bool hasProp(const std::string &key) const {
      if(!dp_props) return false;
      return dp_props->hasVal(key);
    };

    //! clears the value of a \c property
    /*!
       <b>Notes:</b>
         - if no \c property with name \c key exists, a KeyErrorException
	   will be thrown.
	 - if the \c property is marked as \c computed, it will also be removed
	   from our list of \c computedProperties
    */
    void clearProp(const char *key) const {
      std::string what(key);
      clearProp(what);
    };
    //! \overload
    void clearProp(const std::string &key) const {
      STR_VECT compLst;
      if (getPropIfPresent(detail::computedPropName, compLst)) {
	STR_VECT_I svi = std::find(compLst.begin(), compLst.end(), key);
	if (svi != compLst.end()) {
	  compLst.erase(svi);
	  dp_props->setVal(detail::computedPropName, compLst);
	}
      }
      dp_props->clearVal(key);
    }

    //! clears all of our \c computed \c properties
    void clearComputedProps() const {
      STR_VECT compLst;
      if(getPropIfPresent(detail::computedPropName, compLst)) {
	BOOST_FOREACH(const std::string &sv,compLst){
	  dp_props->clearVal(sv);
	}
	compLst.clear();
	dp_props->setVal(detail::computedPropName, compLst);
      }
    }

    //! calculates any of our lazy \c properties
    /*!
      <b>Notes:</b>
        - requires an owning molecule
    */
    void updatePropertyCache(bool strict=true) { (void)strict; }

  protected:
    //! sets our owning molecule
    //void setOwningMol(ROMol *other);
    //! sets our owning molecule
    //void setOwningMol(ROMol &other) {setOwningMol(&other);};
    bool df_isAromatic;
    bool df_isConjugated;
    boost::uint8_t d_bondType;
    boost::uint8_t d_dirTag;
    boost::uint8_t d_stereo;
    atomindex_t d_index;
    atomindex_t d_beginAtomIdx,d_endAtomIdx;
    ROMol *dp_mol;
    Dict *dp_props;
    INT_VECT *dp_stereoAtoms;

    void initBond();
  };

};

//! allows Bond objects to be dumped to streams
extern std::ostream & operator<<(std::ostream& target, const RDKit::Bond &b);

#endif
