/** 
* @file FPCGPHIK.cpp
* @brief feature pool interface for our GP HIK classifier
* @author Alexander Freytag
* @date 02/01/2012
*/

// STL includes
#include <iostream>

// NICE-core includes
#include <core/basics/numerictools.h>
#include <core/basics/Timer.h>

// NICE-vislearning includes
#include "FPCGPHIK.h"

using namespace std;
using namespace NICE;
using namespace OBJREC;


void FPCGPHIK::init ( const NICE::Config *conf, const std::string & s_confSection )
{
  this->verbose = conf->gB( s_confSection, "verbose", false );
  this->useSimpleBalancing = conf->gB( s_confSection, "use_simple_balancing", false );
  this->minSamples = conf->gI( s_confSection, "min_samples", -1 );
  this->performOptimizationAfterIncrement = conf->gB( s_confSection, "performOptimizationAfterIncrement", false );
  
  this->classifier = new GPHIKClassifier(conf, s_confSection);
}

FPCGPHIK::FPCGPHIK( ) 
{
  this->classifier = NULL;
}

FPCGPHIK::FPCGPHIK( const Config *conf, const string & confSection ) 
{
  this->classifier = NULL;
  
  // if no config file was given, we either restore the classifier from an external file, or run ::init with 
  // an emtpy config (using default values thereby) when calling the train-method
  if ( conf != NULL )
  {
    this->init(conf, confSection);
  }
}

FPCGPHIK::~FPCGPHIK()
{
  if ( this->classifier != NULL )
    delete this->classifier;
  this->classifier = NULL;
}

ClassificationResult FPCGPHIK::classify ( Example & pe )
{
  const SparseVector *svec = pe.svec;

  if ( svec == NULL )
    fthrow(Exception, "FPCGPHIK requires example.svec (SparseVector stored in an Example struct)");
 return this->classify( svec ); 
}

ClassificationResult FPCGPHIK::classify ( const NICE::SparseVector * example )
{
  if ( this->classifier == NULL )
    fthrow(Exception, "Classifier not trained yet -- aborting!" );
  
  NICE::SparseVector scores;
  uint result;
  
  double uncertainty;
 
  classifier->classify ( example,  result, scores, uncertainty);
  
  if ( scores.size() == 0 ) {
    fthrow(Exception, "Zero scores, something is likely to be wrong here: svec.size() = " << example->size() );
  }
  
  uint classes = scores.getDim();
  FullVector fvscores(classes);
  
  NICE::SparseVector::const_iterator it;
  for( uint c = 0; c < classes; c++ )
  {
    it = scores.find(c);
    if ( it == scores.end() )
      fvscores[c] = -std::numeric_limits<double>::max();
    else
      fvscores[c] = it->second;
  }

  ClassificationResult r ( fvscores.maxElement(), fvscores );
  r.uncertainty = uncertainty;
  
  if (verbose)
  {
    std::cerr << " FPCGPHIK::classify scores" << std::endl;
    scores.store(std::cerr);
    std::cerr << " FPCGPHIK::classify fvscores" << std::endl;
    fvscores.store(std::cerr);
  }

  return r;
}

/** training process */
void FPCGPHIK::train ( FeaturePool & fp, Examples & examples )
{
  if ( this->classifier == NULL )
  {
    std::cerr << "WARNING -- No config used so far, initialize values with empty config file now..." << std::endl;
    NICE::Config tmpConfEmpty ;
    this->init ( &tmpConfEmpty );
  }  
  
  // we completely ignore the feature pool :)
  //
  initRand(0);
  Vector classCounts;
  int minClass = -1;
  
  if (verbose) 
    std::cerr << "FPCGPHIK::train" << std::endl;

  if ( useSimpleBalancing)
  {
    classCounts.resize( examples.getMaxClassNo()+1 );
    classCounts.set( 0.0 );
    for ( uint i = 0 ; i < examples.size() ; i++ )
      classCounts[ examples[i].first ]++;
    // we need a probability distribution
    //classCounts.normalizeL1();
    // we need the class index of the class with the least non-zero examples
    for ( uint i = 0 ; i < classCounts.size(); i++ )
      if ( (classCounts[i] > 0) && ((minClass < 0) || (classCounts[i] < classCounts[minClass])) )
        minClass = i;
    if (verbose)
    {
      cerr << "Class distribution: " << classCounts << endl;
      cerr << "Class with the least number of examples: " << minClass << endl;
    }
    if(minSamples < 0)
      minSamples = classCounts[minClass];
  }

  // (multi-class) label vector
  Vector y ( examples.size() /* maximum size */ );

  // flat structure of our training data
  std::vector< const SparseVector * > sparseExamples;

  if (verbose)
    cerr << "Converting (and sampling) feature vectors" << endl;
  for ( uint i = 0 ; i < examples.size() ; i++ )
  {
    const Example & example = examples[i].second;
    int classno = examples[i].first;
    
    // simple weird balancing method
    if ( useSimpleBalancing ) 
    {
      double t = randDouble() * classCounts[classno];
      if ( t >= minSamples ) continue;
    }

    y[ sparseExamples.size() ] = classno;
    if ( example.svec == NULL )
      fthrow(Exception, "FPCGPHIK requires example.svec (SparseVector stored in an Example struct)");
    sparseExamples.push_back( example.svec );    
  }

  // we only use a subset for training
  y.resize( sparseExamples.size() );
  
  classifier->train(sparseExamples, y);
}

/** training process */
void FPCGPHIK::train ( const std::vector< const SparseVector *> & examples, std::map< uint, NICE::Vector > & binLabels )
{
  classifier->train(examples, binLabels);
}

void FPCGPHIK::clear ()
{
  if ( classifier != NULL )
    delete classifier;
  classifier = NULL;
}

FeaturePoolClassifier *FPCGPHIK::clone () const
{
  fthrow(Exception, "FPCGPHIK: clone() not yet implemented" );

  return NULL;
}

void FPCGPHIK::predictUncertainty( Example & pe, double & uncertainty )
{
  const SparseVector *svec = pe.svec;  
  if ( svec == NULL )
    fthrow(Exception, "FPCGPHIK requires example.svec (SparseVector stored in an Example struct)");
  classifier->predictUncertainty(svec, uncertainty);
}
   
void FPCGPHIK::predictUncertainty( const NICE::SparseVector * example, double & uncertainty )
{  
  classifier->predictUncertainty(example, uncertainty);
}

///////////////////// INTERFACE PERSISTENT /////////////////////
// interface specific methods for store and restore
///////////////////// INTERFACE PERSISTENT ///////////////////// 
void FPCGPHIK::restore ( std::istream & is, int format )
{
  if (is.good())
  {    
    std::string tmp;
    is >> tmp; //class name 
    
    if ( ! this->isStartTag( tmp, "FPCGPHIK" ) )
    {
      std::cerr << " WARNING - attempt to restore FPCGPHIK, but start flag " << tmp << " does not match! Aborting... " << std::endl;
      throw;
    } 
    
    is.precision (numeric_limits<double>::digits10 + 1);
    
    bool b_endOfBlock ( false ) ;
    
    while ( !b_endOfBlock )
    {
      is >> tmp; // start of block 
      
      if ( this->isEndTag( tmp, "FPCGPHIK" ) )
      {
        b_endOfBlock = true;
        continue;
      } 

      tmp = this->removeStartTag( tmp );

      if ( tmp.compare("classifier") == 0 )
      {
        if ( classifier == NULL )
          classifier = new NICE::GPHIKClassifier();
        
        //then, load everything that we stored explicitely,
        // including precomputed matrices, LUTs, eigenvalues, ... and all that stuff
        classifier->restore(is, format);  
          
        is >> tmp; // end of block 
        tmp = this->removeEndTag ( tmp );
      }
      else if ( tmp.compare("performOptimizationAfterIncrement") == 0 )
      {
        is >> performOptimizationAfterIncrement;        
        is >> tmp; // end of block 
        tmp = this->removeEndTag ( tmp );
      }      
      else
      {
      std::cerr << "WARNING -- unexpected FPCGPHIK object -- " << tmp << " -- for restoration... aborting" << std::endl;
      throw;
      }
    } // while-loop
  }
  else
  {
    std::cerr << "FPCGPHIK::restore -- InStream not initialized - restoring not possible!" << std::endl;
  }
}

void FPCGPHIK::store ( std::ostream & os, int format ) const
{
  if (os.good())
  {
    os.precision (numeric_limits<double>::digits10 + 1);
    
    // show starting point
    os << this->createStartTag( "FPCGPHIK" ) << std::endl;        
    
    os << this->createStartTag( "classifier" ) << std::endl;
    classifier->store(os, format);
    os << this->createEndTag( "classifier" ) << std::endl;
    
    os << this->createStartTag( "performOptimizationAfterIncrement" ) << std::endl;  
    os << performOptimizationAfterIncrement << std::endl;
    os << this->createEndTag( "performOptimizationAfterIncrement" ) << std::endl;     
    
    // done
    os << this->createEndTag( "FPCGPHIK" ) << std::endl;
  }
  else
  {
    std::cerr << "OutStream not initialized - storing not possible!" << std::endl;
  }
}

///////////////////// INTERFACE ONLINE LEARNABLE (SIMILAR) /////////////////////
// interface specific methods for incremental extensions
///////////////////// INTERFACE ONLINE LEARNABLE (SIMILAR) /////////////////////

void FPCGPHIK::addExample( const Example & pe, const double & label)
{
  const SparseVector *svec = pe.svec;
  classifier->addExample(svec, label, this->performOptimizationAfterIncrement);
}

void FPCGPHIK::addMultipleExamples( Examples & newExamples)
{
  //are new examples available? If not, nothing has to be done
  if ( newExamples.size() < 1)
    return;
  
  // (multi-class) label vector
  NICE::Vector y ( newExamples.size() );

  // flat structure of our training data
  std::vector< const SparseVector * > sparseExamples;

  if (verbose)
    cerr << "Converting (and sampling) feature vectors" << endl;
  for ( uint i = 0 ; i < newExamples.size() ; i++ )
  {
    const Example & example = newExamples[i].second;
    int classno = newExamples[i].first;

    y[ i ] = classno;
    if ( example.svec == NULL )
      fthrow(Exception, "FPCGPHIK requires example.svec (SparseVector stored in an Example struct)");
    sparseExamples.push_back( example.svec );    
  }  
  
  classifier->addMultipleExamples(sparseExamples, y, this->performOptimizationAfterIncrement);  
}