#ifndef MBARIVISUALOBJECTDB_H_
#define MBARIVISUALOBJECTDB_H_

#include "Image/Image.H"
#include "MbariVisualObject.H"
#include "MbariVisualObjectMatch.H"

#include <cstring>
#include <iosfwd>
#include <vector> // for std::pair

class KDTree;

//! VisualObjectDatabase
class MbariVisualObjectDB
{
public:
  //! Constructor; use operator>> and operator<< to load/save
  MbariVisualObjectDB();

  //! Destructor
  ~MbariVisualObjectDB();

  //! Easy loading from file
  /*! This will just open a file and use operator>> on it. Returns
    true on success. */
  bool loadFrom(const std::string& fname);

  //! Easy saving to file
  /*! This will just open a file and use operator<< on it. Returns
    true on success. */
  bool saveTo(const std::string& fname);

  //! name access methods
  inline std::string getName() const;
  inline void setName(const std::string& name);

  //! get number of objects in the database
  inline uint numObjects() const;

  //! object getter
  inline const rutz::shared_ptr<MbariVisualObject>& getObject(const uint index) const;

  //! object setter
  inline void setObject(const uint index, const rutz::shared_ptr<MbariVisualObject>& obj);

  //! erase object
  inline void eraseObject(const uint index);

  //! set relative filepath for visual object images
  inline void setFilePath(const std::string filepath);

  //! object getter by name
  /*! Returns an uninitialized rutz::shared_ptr is not found. */
  inline rutz::shared_ptr<MbariVisualObject> getObject(const std::string objectName) const;

  //! add object
  /*! @return true if the object was added, false if it was not added
    because an object with the same name already exists in the
    database. This invalidates any internal KDTree we may have. */
  bool addObject(const rutz::shared_ptr<MbariVisualObject>& obj);

  //! Build a giant internal KDTree from all our Keypoints
  /*! This will automatically be called by getObjectMatches() if an
    up-to-date KDTree is not internally available. This function is
    made public because it may take a while to run, and is best run
    just after loading a DB, rather than on the first match
    attempt. If an up-to-date KDTree is already internally available,
    this is a no-op. */
  void buildKDTree();

  //! find match for the keypoints in the scene
  /*! @param obj the VisualObject to match against the objects in our database.
    @param matches list of matches which satisfy all criteria, sorted
    by matching score. Note: the list is initially cleared.
    @param algo the algorithm to use for matching (see VisualObjectMatch.H).
    @param maxn the maximum number of object matches to return. NOTE:
    we will return the first maxn matches found without exploring the
    database any further. If this is not desirable, use a large
    matchn. You can also use sortbypf below to attempt that these
    first matches will indeed be the best ones.
    @param kcoeff coefficient for keypoint distance, used to score a
    match. See VisualObjectMatch::getScore().
    @param acoeff coefficient for affine distance, used to score a
    match. See VisualObjectMatch::getScore().
    @param minscore min acceptable VisualObjectMatch::getScore(kcoeff, acoeff).
    @param mink minimum number of matching keypoints in an object match.
    @param kthresh threshold to use for keypoint selection in obj; see
    VisualObject constructor and ScaleSpace.
    @param sortbypf sort our database by similarity of preattentive
    visual features if true. This is incompatible with KDTree-based
    matching algos, which would not benefit from this sorting.
    @return the number of matches found (== matches.size()) */
  uint getObjectMatches(const rutz::shared_ptr<MbariVisualObject>& obj,
                        std::vector< rutz::shared_ptr<MbariVisualObjectMatch> >& matches,
                        const VisualObjectMatchAlgo algo = VOMA_SIMPLE,
                        const uint maxn = 5U,
                        const float kcoeff = 0.5F,
                        const float acoeff = 0.5F,
                        const float minscore = 1.0F,
                        const uint mink = 3U,
                        const uint kthresh = 6U,
                        const bool sortbypf = false);


  void createMbariVisualObjectDB(std::istream& is, MbariVisualObjectDB& vdb, std::string filepath = "");
  void createMbariVODB(std::istream& is, MbariVisualObjectDB& vdb, std::string filepath = "");
  
  bool addOneImageDB(std::string classe, std::string fileName);
  bool addOneImage(std::string classe, std::string fileName);

private:
  std::string itsName;
  std::vector< rutz::shared_ptr<MbariVisualObject> > itsObjects;  // objects
  rutz::shared_ptr<KDTree> itsKDTree;
  std::vector< std::pair<uint,uint> > itsKDindices; // <objidx, kpidx>

  // sort object db according to preattentive feature vector similarity
  void computeSortedIndices(std::vector<uint>& indices,
                            const rutz::shared_ptr<MbariVisualObject>& obj) const;

  friend std::istream& operator>>(std::istream& is, MbariVisualObjectDB& vdb);
  friend std::ostream& operator<<(std::ostream& os, const MbariVisualObjectDB& vdb);
};

// ######################################################################
// VisualObjectDB I/O functions
// ######################################################################

//! Load a VisualObjectDB from an istream
std::istream& operator>>(std::istream& is, MbariVisualObjectDB& vdb);

//! Save a VisualObjectDB to an ostream
std::ostream& operator<<(std::ostream& os, const MbariVisualObjectDB& vdb);


// ######################################################################
// VisualObjectDB inline function implementations
// ######################################################################

inline std::string MbariVisualObjectDB::getName() const
{ return itsName; }

inline void MbariVisualObjectDB::setName(const std::string& name)
{ itsName = name; }

inline uint MbariVisualObjectDB::numObjects() const
{ return itsObjects.size(); }

inline void MbariVisualObjectDB::eraseObject(const uint index)
{
  ASSERT(index < itsObjects.size());
  itsObjects.erase(itsObjects.begin() + index);
}

inline const rutz::shared_ptr<MbariVisualObject>&
MbariVisualObjectDB::getObject(const uint index) const
{
  return itsObjects[index];
}

inline rutz::shared_ptr<MbariVisualObject>
MbariVisualObjectDB::getObject(const std::string objectName) const
{
  std::vector< rutz::shared_ptr<MbariVisualObject> >::const_iterator
    vo = itsObjects.begin(), stop = itsObjects.end();

  while (vo != stop)
    {
      if ((*vo)->getName().compare(objectName) == 0) return *vo;
      ++ vo;
    }

  // not found, return an uninitialized rutz::shared_ptr:
  return rutz::shared_ptr<MbariVisualObject>();
}

inline void MbariVisualObjectDB::setObject(const uint index, const rutz::shared_ptr<MbariVisualObject>& obj)
{
  ASSERT(index < itsObjects.size());
  itsObjects[index] = obj;
}

inline void MbariVisualObjectDB::setFilePath(const std::string filepath) {

  std::vector< rutz::shared_ptr<MbariVisualObject> >::const_iterator
    vo = itsObjects.begin(), stop = itsObjects.end();

  while (vo != stop)
    {
      //if ((*vo)->getName()) return *vo;
      (*vo)->setImageFname(filepath + (*vo)->getImageFname());
      ++ vo;
    }

}

#endif /*MBARIVISUALOBJECTDB_H_*/
