/*
 * RenderItemDistanceMetric.h
 *
 *  Created on: Feb 16, 2009
 *      Author: struktured
 */

#ifndef RenderItemDISTANCEMETRIC_H_
#define RenderItemDISTANCEMETRIC_H_

#include "Common.hpp"
#include "Renderable.hpp"
#include <limits>
#include <functional>
#include <map>


/// Compares two render items and returns zero if they are virtually equivalent and large values
/// when they are dissimilar. If two render items cannot be compared, NOT_COMPARABLE_VALUE is returned.
class RenderItemDistanceMetric : public std::binary_function<const RenderItem*, const RenderItem*, double> {
public:
  virtual ~RenderItemDistanceMetric() { }
  const static double NOT_COMPARABLE_VALUE;
  virtual double operator()(const RenderItem * r1, const RenderItem * r2) const = 0;
  virtual TypeIdPair typeIdPair() const = 0;
};

// A base class to construct render item distance metrics. Just specify your two concrete
// render item types as template parameters and override the computeDistance() function.
template <class R1, class R2>
class RenderItemDistance : public RenderItemDistanceMetric {

protected:
// Override to create your own distance fmetric for your specified custom types.
virtual double computeDistance(const R1 * r1, const R2 * r2) const = 0;

public:

inline virtual double operator()(const RenderItem * r1, const RenderItem * r2) const {
	if (supported(r1, r2))
		return computeDistance(dynamic_cast<const R1*>(r1), dynamic_cast<const R2*>(r2));
	else if (supported(r2,r1))
		return computeDistance(dynamic_cast<const R1*>(r2), dynamic_cast<const R2*>(r1));
	else
		return NOT_COMPARABLE_VALUE;
}

// Returns true if and only if r1 and r2 are the same type as or derived from R1, R2 respectively
inline bool supported(const RenderItem * r1, const RenderItem * r2) const {
	return dynamic_cast<const R1*>(r1) && dynamic_cast<const R2*>(r2);
	//return typeid(r1) == typeid(const R1 *) && typeid(r2) == typeid(const R2 *);
}

inline TypeIdPair typeIdPair() const {
	return TypeIdPair(typeid(const R1*).name(), typeid(const R2*).name());
}

};


class RTIRenderItemDistance : public RenderItemDistance<RenderItem, RenderItem> {
public:

	RTIRenderItemDistance() {}
	virtual ~RTIRenderItemDistance() {}

protected:
	virtual inline double computeDistance(const RenderItem * lhs, const RenderItem * rhs) const {
		if (typeid(*lhs) == typeid(*rhs)) {
			//std::cerr << typeid(*lhs).name() << " and " << typeid(*rhs).name() <<  "are comparable" << std::endl;

			return 0.0;
		}
		else {
			//std::cerr << typeid(*lhs).name() << " and " << typeid(*rhs).name() <<  "not comparable" << std::endl;
			return NOT_COMPARABLE_VALUE;
		}
	}


};



class ShapeXYDistance : public RenderItemDistance<Shape, Shape> {

public:

	ShapeXYDistance() {}
	virtual ~ShapeXYDistance() {}

protected:

	virtual inline double computeDistance(const Shape * lhs, const Shape * rhs) const {
			return (meanSquaredError(lhs->x, rhs->x) + meanSquaredError(lhs->y, rhs->y)) / 2;
	}

};


class MasterRenderItemDistance : public RenderItemDistance<RenderItem, RenderItem> {

typedef std::map<TypeIdPair, RenderItemDistanceMetric*> DistanceMetricMap;
public:

	MasterRenderItemDistance() {}
    virtual ~MasterRenderItemDistance() {
        for (DistanceMetricMap::iterator it = _distanceMetricMap.begin(); it != _distanceMetricMap.end(); ++it)
            delete (it->second);
        _distanceMetricMap.clear();
    }

	inline void addMetric(RenderItemDistanceMetric * fun) {
		_distanceMetricMap[fun->typeIdPair()] = fun;
	}

protected:
	virtual inline double computeDistance(const RenderItem * lhs, const RenderItem * rhs) const {

		RenderItemDistanceMetric * metric;

		TypeIdPair pair(typeid(lhs), typeid(rhs));


		// If specialized metric exists, use it to get higher granularity
		// of correctness
		if (_distanceMetricMap.count(pair)) {
			metric = _distanceMetricMap[pair];
		} else if (_distanceMetricMap.count(pair = TypeIdPair(typeid(rhs), typeid(lhs)))) {
			metric = _distanceMetricMap[pair];
		} else { // Failing that, use rtti && shape distance if its a shape type

			const double rttiError = _rttiDistance(lhs,rhs);

			/// @bug This is a non elegant approach to supporting shape distance
			if (rttiError == 0 && _shapeXYDistance.supported(lhs,rhs))
			   return _shapeXYDistance(lhs, rhs);
			else return rttiError;
		}

		return (*metric)(lhs, rhs);
	}

private:
	mutable RTIRenderItemDistance _rttiDistance;
	mutable ShapeXYDistance _shapeXYDistance;
	mutable DistanceMetricMap _distanceMetricMap;
};

#endif /* RenderItemDISTANCEMETRIC_H_ */
