/** 
 * @file LFPatches.cpp
 * @brief simple patch based approach
 * @author Erik Rodner
 * @date 02/06/2008

 */
#include <core/image/RectangleT.h>
#include <core/image/ImageTools.h>
#include <iostream>
#include <sstream>

#include "vislearning/features/localfeatures/LFPatches.h"

#include "vislearning/features/localfeatures/IDRandomSampling.h"
#include "vislearning/features/localfeatures/IDKLTSampling.h"
#include "vislearning/features/localfeatures/IDSIFTSampling.h"

#include "vislearning/image/ImagePyramid.h"

using namespace OBJREC;

using namespace std;
using namespace NICE;

void LFPatches::setDetector ( const int & _detectormode, const NICE::Config * conf)
{

  if ( this->id != NULL )
  {
    delete this->id;
    this->id = NULL;
  }
  
  if (_detectormode == 0)
  {
    this->id = new IDRandomSampling(conf, this->numPatches);
  }
  else if (_detectormode == 1)
  {
    this->id = new IDKLTSampling(conf, this->numPatches);    
  }
  else if (_detectormode == 2)
  {
    this->id = new IDSIFTSampling(conf);    
  }
  else
  {
    this->id = new IDRandomSampling(conf, this->numPatches);
  }  
}

void LFPatches::setNormalization( const std::string & _normalization_s) 
{
  if (_normalization_s == "n01")
          normalization = NORMALIZE_N01;
  else if (_normalization_s == "stddev")
          normalization = NORMALIZE_STDDEV;
  else if (_normalization_s == "mean")
          normalization = NORMALIZE_MEAN;
  else if (_normalization_s == "none")
          normalization = NORMALIZE_NONE;
  else
  {
          fprintf(stderr, "LFPatches::LFPatches: unknown normalization method\n");
          exit(-1);
  }  
}

///////////////////// ///////////////////// /////////////////////
//                   CONSTRUCTORS / DESTRUCTORS
///////////////////// ///////////////////// /////////////////

LFPatches::LFPatches()
{
  this->numPatches  = -1;
  this->xsize        = 11;
  this->ysize        = 11;
  this->maxLevels    = 10;
  this->scaleSpacing = sqrt(sqrt(2));
  this->detectormode = 0;  
  this->id = NULL;
  
  std::string normalization_s = "n01";
  this->setNormalization ( normalization_s );
  
  //TODO initialization useful here?
  srand(time(NULL));
}


LFPatches::LFPatches(const Config *conf, int _numPatches) :
	numPatches(_numPatches)
{
	this->xsize = conf->gI("Patches", "xsize", 11);
	this->ysize = conf->gI("Patches", "ysize", 11);
	this->maxLevels = conf->gI("Patches", "levels", 10);
	this->scaleSpacing = conf->gD("Patches", "scale_spacing", sqrt(sqrt(2)));
	this->detectormode = conf->gI("Patches", "detector", 0);
        this->setDetector ( this->detectormode, conf );
        
        std::string normalization_s = conf->gS("Patches", "normalization", "n01");
        this->setNormalization ( normalization_s );


	srand(time(NULL));

}

LFPatches::~LFPatches()
{
  if ( this->id != NULL )
  {
    delete this->id;
    this->id = NULL;
  }
}

///////////////////// ///////////////////// /////////////////////
//                      FEATURE STUFF
///////////////////// ///////////////////// ////////////////// 

int LFPatches::getDescSize() const
{
	return xsize * ysize;
}

int LFPatches::extractFeatures(const NICE::Image & img, VVector & features,
		VVector & positions) const
{
	ImagePyramid imp(img, maxLevels, scaleSpacing, xsize, ysize);
	//imp.show();
	VVector positions_tmp;
	if(positions.size()<=0)
	{
		id->getRandomInterests(imp, positions_tmp,numPatches);
	}
	else
	{
		positions_tmp=positions;
	}
	correctPositions(imp, positions_tmp, positions);
	calcDescriptors(imp, positions, features);
	return features.size();
}

void LFPatches::correctPositions(const ImagePyramid & imp, VVector & positions,
		VVector & positions_corrected) const
{
	positions_corrected.clear();
	int numlevels = imp.getNumLevels();
	for (size_t i = 0; i < positions.size(); i++)
	{
		const NICE::Vector & position = positions[i];
		if ((size_t) position[2] < numlevels)
		{

			const NICE::Image & img = imp.getLevel((size_t) position[2]);
			double xl, yl;
			imp.getLevelCoordinates(position[0], position[1],
					(size_t) position[2], xl, yl);
			int x = (int) xl;
			int y = (int) yl;
			x -= xsize / 2;
			y -= ysize / 2;

			if ((x >= 0) && (y >= 0) && (x <= img.width() - xsize) && (y
					<= img.height() - ysize))
			{
				NICE::Vector position_new(3);
				imp.getOriginalCoordinates(x, y,
					(size_t) position[2], position_new[0], position_new[1]);
				position_new[2] = position[2];
				positions_corrected.push_back(position_new);
			}
		}
	}
}

void LFPatches::calcDescriptors(const ImagePyramid & imp, VVector & positions,
		VVector & features) const
{
	NICE::Vector desc(getDescSize());
	for (VVector::iterator i = positions.begin(); i != positions.end();)
	{
		bool deletePosition = false;

		if (calcDescriptor(imp, *i, desc) < 0)
		{
			deletePosition = true;
		}
		else
		{
			features.push_back(desc);
		}

		if (deletePosition)
		{
			i = positions.erase(i);
		}
		else
		{
			i++;
		}
	}
}

int LFPatches::calcDescriptor(const ImagePyramid & imp,
		const NICE::Vector & position, NICE::Vector & desc) const
{
	const NICE::Image & img = imp.getLevel((size_t) position[2]);
	double xl, yl;
	imp.getLevelCoordinates(position[0], position[1], (uint) position[2], xl,
			yl);
	int x = (int) xl;
	int y = (int) yl;

	if ((x < 0) || (y < 0) || (x > img.width() - xsize) || (y > img.height()
			- ysize))
	{
		return -1;
	}
	double mean = 0.0;
	double stddev = 0.0;

	if (normalization != NORMALIZE_NONE)
	{
		// compute mean
		for (int yi = y; yi < y + ysize; yi++)
			for (int xi = x; xi < x + xsize; xi++)
				mean += img.getPixel(xi, yi);

		mean /= (xsize * ysize);

		if (normalization != NORMALIZE_MEAN)
		{
			// compute stddev
			stddev = 0.0;
			for (int yi = y; yi < y + ysize; yi++)
				for (int xi = x; xi < x + xsize; xi++)
				{
					double d = img.getPixel(xi, yi) - mean;
					stddev += d * d;
				}

			if (stddev < 10e-5)
				return -1;

			stddev /= xsize * ysize;
			stddev = sqrt(stddev);
		}
	}

	if (normalization == NORMALIZE_STDDEV)
	{
		// normalize pixel values
		int k = 0;
		for (int yi = y; yi < y + ysize; yi++)
			for (int xi = x; xi < x + xsize; xi++, k++)
				desc[k] = (img.getPixel(xi, yi) - mean) / stddev + mean;
	}
	else if (normalization == NORMALIZE_N01)
	{
		// normalize pixel values
		int k = 0;
		for (int yi = y; yi < y + ysize; yi++)
			for (int xi = x; xi < x + xsize; xi++, k++)
				desc[k] = (img.getPixel(xi, yi) - mean) / stddev;
	}
	else if (normalization == NORMALIZE_MEAN)
	{

		// normalize pixel values
		int k = 0;
		for (int yi = y; yi < y + ysize; yi++)
			for (int xi = x; xi < x + xsize; xi++, k++)
				desc[k] = (img.getPixel(xi, yi) - mean);

	}
	else
	{
		int k = 0;
		for (int yi = y; yi < y + ysize; yi++)
			for (int xi = x; xi < x + xsize; xi++, k++)
				desc[k] = img.getPixel(xi, yi);
	}

	return 0;
}

void LFPatches::visualizeFeatures(NICE::Image & mark,
		const VVector & positions, size_t color) const
{
	for (size_t i = 0; i < positions.size(); i++)
	{
		const NICE::Vector & pos = positions[i];
		double s = pow(scaleSpacing, pos[2]);
		int x = (int) (pos[0]);
		int y = (int) (pos[1]);
		int w = (int) (s * xsize);
		int h = (int) (s * ysize);

		RectangleT<Ipp8u> rect(Coord(x, y), Coord(x + w, y + w));
		mark.draw(rect, (unsigned char) color);
	}
}

void LFPatches::visualize(NICE::Image & img, const NICE::Vector & feature) const
{
	if (feature.size() != (size_t) xsize * ysize)
	{
		fprintf(stderr, "LFPatches::visualize implementation failure\n");
		exit(-1);
	}

	img.resize(xsize, ysize);

	double max = -numeric_limits<double>::max();
	double min = numeric_limits<double>::max();
	int k = 0;
	for (k = 0; k < xsize * ysize; k++)
	{
		if (feature[k] > max)
			max = feature[k];
		if (feature[k] < min)
			min = feature[k];
	}

	k = 0;
	for (int y = 0; y < ysize; y++)
		for (int x = 0; x < xsize; x++, k++)
		{
			double val = ((feature[k] - min) * 255 / (max - min));
			img.setPixel(x, y, (int) val);
		}
}

///////////////////// INTERFACE PERSISTENT /////////////////////
// interface specific methods for store and restore
///////////////////// INTERFACE PERSISTENT ///////////////////// 

void LFPatches::restore ( std::istream & is, int format )
{ 
  fthrow ( Exception, "LFPatches::restore not implemented yet." );
  //TODO  
}

void LFPatches::store ( std::ostream & os, int format ) const
{ 
  fthrow ( Exception, "LFPatches::store not implemented yet." );
  //TODO  
}

void LFPatches::clear ()
{ 
  if ( this->id != NULL )
  {
    delete this->id;
    this->id = NULL;
  }
}
