/*! 
 *   \file  CInventory.cpp
 *   \brief Defines class to hold inventory.
 *    
 *   \details  
 *   CInventory loads the inventory and keeps it in memory. 
 *
 *   \date      October 24, 2011
 *   \copyright eBay Research Labs.
 */

// Include boost-specific headers.
#include "boost/filesystem.hpp"   // Includes all needed Boost.Filesystem declarations
#include <boost/algorithm/string/case_conv.hpp>
#include "boost/thread/mutex.hpp"
#include "boost/thread/recursive_mutex.hpp"

#include "Log.h"

// Include standard C++ headers.
#include <iostream> // For cout
#include <iomanip>  // For setprecision 
#include <sstream>  // For ostringstream
#include <cstdio>   // popen

// Include openCV-specific headers.
#include <opencv2/contrib/contrib.hpp>

#include "CInventory.h"
#include "CFeatureExtraction.h"
#include <unordered_map>

#include "UtilitiesWeb.h"
#include "Base64.h"

// #include <boost/thread/thread.hpp>
#include <boost/date_time/time.hpp>


// Namespaces.
using namespace std;
using namespace cv;
namespace bfs = boost::filesystem; 

// initialize our static singleton object to null
CInventory *CInventory::s_instance[100] = {0};

std::string CInventory::s_root;


/*!
 *   \brief Constructor. 
 */
CInventory::CInventory(int siteid, const std::string& root):  m_siteId(siteid), m_isLoaded(false)
{
  // Initialize.
  m_categories.clear();
  m_ftrMapper = 0;
  LoadFtrMappers(root);
}

/*!
 *   \brief  Destructor.
 */
CInventory::~CInventory()
{
  if (m_ftrMapper)
    delete m_ftrMapper;
}

CInventory* CInventory::Instance(int siteid)
{
  CInventory** p_instance= &s_instance[siteid];
  if (!*p_instance) {
    *p_instance = new CInventory(siteid, s_root);
  }
	
  return *p_instance;
}

bool CInventory::IsLoaded(void) const
{
  return m_isLoaded;
}

int CInventory::GetCategoryCount(void) const
{
  return (int)m_categories.size();
}


size_t CInventory::Category::getNumberOfItems() const
{
  return  m_itemSignatureVector.size();
}


CInventory::Category* CInventory::createNewCategory(const std::string& cat)
{
  Mutex::scoped_lock lock(m_inventoryMutex);
  Category* new_cat = new Category(cat, getNumericSiteID());
  if (new_cat)
    {
      m_categories.push_back(new_cat);
      m_cat_map[new_cat->getID()] = m_categories.size()-1;
    }
  return new_cat;
}

CInventory::Category* CInventory::getCategory(const std::string& cat) const
{
  CategoryIndexMap::const_iterator it(m_cat_map.find(cat));
  if (it == m_cat_map.end())
    return 0;
  else
    return m_categories[it->second];
}

/*!
 *   \brief Load inventory from given directory. Each category will be in a separate folder.
 *
 *   <\param [in]  dedupeFlag  Flag to indicate whether to dedupe based on MD5 or not. Default = false.
 */
bool CInventory::LoadInventory(std::istream& in, const std::string& cat_id)
{
    
  bool status = true;

  ostringstream s;
  
  Category* cat = createNewCategory(cat_id);

  MANTIS_REPORT_EVENT("INV_LOAD_START", "0", s.str().c_str());
 
  TickMeter timer;
  timer.start();
  
  // Read files for each category.
  cat->doLoad(in);
  
  timer.stop();
  
  s.clear();
  s << timer;

  {
    Mutex::scoped_lock  lock(g_io_mutex);			
    std::cerr<< "Load of category " << cat_id << " took: " << s.str() << ", images : " << cat->getNumberOfItems() << std::endl << std::flush;
  }
  
  m_isLoaded = true;

  return status;
}

void CInventory::Category::doLoad(std::istream& in)
{
  int numErrors = 0;

#define INITIAL_SIG_SIZE size_t(1024UL*1024UL) 

  m_itemSignatureVector.reserve(INITIAL_SIG_SIZE);

  const int bufsize = 4086;
   
  std::string read_buf;

  size_t  len = 0;
  size_t read;
	
  while (std::getline(in, read_buf)) {
    // end token - we need one as we can't use EOF
    if (strcmp(&read_buf[0], "END_VISION_DUMP")==0) {
      cerr << "Received  END_VISION_DUMP\n";
      break;
    }

    // first line is a list of item ids as comma-separated string. We store those literally.
    // next line is a base64 encoded signature
    if (read_buf[0]!='[')
      cerr << "Format error : expected [item_id, ...], got: " << read_buf << endl;

    CSignature* sig = LoadInventoryItem(in);

    sig->m_itemIDs = string(&read_buf[1], strlen(&read_buf[1])-1);
	  
    {
      // PROTECTED BLOCK
      Mutex::scoped_lock lock(getMutex());
      m_itemSignatureVector.push_back(sig);
    }	  
  } // outer for loop
}

CInventory::Category::Category(const bfs::path& rootDir, int siteid) : 
  m_siteID(siteid),
  m_categoryID(rootDir.stem().string()), m_catPath(rootDir), m_currTime(time(0)) 
{
}

CSignature* CInventory::Category::LoadInventoryItem(std::istream& s)
{
  std::string b64E;
  std::getline(s, b64E);

  CFeatureExtraction fx;

  fx.DeserializeSignature(b64E);
  return fx.release();
}

bool CInventory::LoadFtrMappers(const std::string& contextRootDir)
{
  if (m_ftrMapper)
    {
      delete m_ftrMapper;
      m_ftrMapper = 0;
    }
  
  m_ftrMapper = new (std::nothrow) CFeatureMapper();
  if (!m_ftrMapper)
    return false;
 
  // Load mapping models.
  m_ftrMapper->LoadFeatureMappers( boost::filesystem::path(contextRootDir) / "learntContext");
  
  return true;
}

boost::recursive_mutex g_io_mutex;
