/*
 * RenderItemMergeFunction.hpp
 *
 *  Created on: Feb 16, 2009
 *      Author: struktured
 */

#ifndef RenderItemMergeFunction_HPP_
#define RenderItemMergeFunction_HPP_

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


template <class T>
inline T interpolate(T a, T b, float ratio)
{
    return (ratio*a + (1-ratio)*b) * 0.5;
}

template <>
inline int interpolate(int a, int b, float ratio)
{
    return (int)(ratio*(float)a + (1-ratio)*(float)b) * 0.5;
}

template <>
inline bool interpolate(bool a, bool b, float ratio)
{
    return (ratio >= 0.5) ? a : b;
}


/// Merges 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 RenderItemMergeFunction {
public:
  virtual RenderItem * operator()(const RenderItem * r1, const RenderItem * r2, double ratio) const = 0;
  virtual TypeIdPair typeIdPair() const = 0;
};

/// A base class to construct render item distance mergeFunctions. Just specify your two concrete
/// render item types as template parameters and override the computeMerge() function.
template <class R1, class R2=R1, class R3=R2>
class RenderItemMerge : public RenderItemMergeFunction {

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

public:

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

/// Returns true if and only if r1 and r2 are of type R1 and R2 respectively.
inline bool supported(const RenderItem * r1, const RenderItem * r2) const {
	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 ShapeMerge : public RenderItemMerge<Shape> {

public:

	ShapeMerge() {}
	virtual ~ShapeMerge() {}

protected:

	virtual inline Shape * computeMerge(const Shape * lhs, const Shape * rhs, double ratio) const {

    	Shape * ret = new Shape();
	Shape & target = *ret;

	target.x = interpolate(lhs->x, rhs->x, ratio);
        target.y = interpolate(lhs->y, rhs->y, ratio);
	target.a = interpolate(lhs->a, rhs->a, ratio);
        target.a2 = interpolate(lhs->a2, rhs->a2, ratio);
        target.r = interpolate(lhs->r, rhs->r, ratio);
        target.r2 = interpolate(lhs->r2, rhs->r2, ratio);
        target.g = interpolate(lhs->g, rhs->g, ratio);
        target.g2 = interpolate(lhs->g2, rhs->g2, ratio);
        target.b = interpolate(lhs->b, rhs->b, ratio);
        target.b2 = interpolate(lhs->b2, rhs->b2, ratio);

        target.ang = interpolate(lhs->ang, rhs->ang, ratio);
        target.radius = interpolate(lhs->radius, rhs->radius, ratio);

        target.tex_ang = interpolate(lhs->tex_ang, rhs->tex_ang, ratio);
        target.tex_zoom = interpolate(lhs->tex_zoom, rhs->tex_zoom, ratio);

        target.border_a = interpolate(lhs->border_a, rhs->border_a, ratio);
        target.border_r = interpolate(lhs->border_r, rhs->border_r, ratio);
        target.border_g = interpolate(lhs->border_g, rhs->border_g, ratio);
        target.border_b = interpolate(lhs->border_b, rhs->border_b, ratio);

        target.sides = interpolate(lhs->sides, rhs->sides, ratio);

        target.additive = interpolate(lhs->additive, rhs->additive, ratio);
        target.textured = interpolate(lhs->textured, rhs->textured, ratio);
        target.thickOutline = interpolate(lhs->thickOutline, rhs->thickOutline, ratio);
        target.enabled = interpolate(lhs->enabled, rhs->enabled, ratio);

        target.masterAlpha = interpolate(lhs->masterAlpha, rhs->masterAlpha, ratio);
        target.imageUrl = (ratio > 0.5) ? lhs->imageUrl : rhs->imageUrl, ratio;

        return ret;
	}
};

class BorderMerge : public RenderItemMerge<Border> {

    public:

        BorderMerge() {}
        virtual ~BorderMerge() {}

    protected:

        virtual inline Border * computeMerge(const Border * lhs, const Border * rhs, double ratio) const
        {
            Border * ret = new Border();
		
	    Border & target = *ret;

            target.inner_a = interpolate(lhs->inner_a, rhs->inner_a, ratio);
            target.inner_r = interpolate(lhs->inner_r, rhs->inner_r, ratio);
            target.inner_g = interpolate(lhs->inner_g, rhs->inner_g, ratio);
            target.inner_b = interpolate(lhs->inner_b, rhs->inner_b, ratio);
            target.inner_size = interpolate(lhs->inner_size, rhs->inner_size, ratio);

            target.outer_a = interpolate(lhs->outer_a, rhs->outer_a, ratio);
            target.outer_r = interpolate(lhs->outer_r, rhs->outer_r, ratio);
            target.outer_g = interpolate(lhs->outer_g, rhs->outer_g, ratio);
            target.outer_b = interpolate(lhs->outer_b, rhs->outer_b, ratio);
            target.outer_size = interpolate(lhs->outer_size, rhs->outer_size, ratio);

            target.masterAlpha = interpolate(lhs->masterAlpha, rhs->masterAlpha, ratio);

            return ret;
        }
};


class WaveformMerge : public RenderItemMerge<Waveform> {

    public:

        WaveformMerge() {}
        virtual ~WaveformMerge() {}

    protected:

	/// @BUG unimplemented
        virtual inline Waveform * computeMerge(const Waveform * lhs, const Waveform * rhs, double ratio) const
        {
		return 0;
/*
            Waveform * ret = new Waveform();
	    Waveform & target = *ret;

            target.additive = interpolate(lhs->additive, rhs->additive, ratio);
            target.dots = interpolate(lhs->dots, rhs->dots, ratio);
            target.samples = (rhs->samples > lhs-> samples) ? lhs->samples : rhs->samples;
            target.scaling = interpolate(lhs->scaling, rhs->scaling, ratio);
            target.sep = interpolate(lhs->sep, rhs->sep, ratio);
            target.smoothing = interpolate(lhs->smoothing, rhs->smoothing, ratio);
            target.spectrum = interpolate(lhs->spectrum, rhs->spectrum, ratio);
            target.thick = interpolate(lhs->thick, rhs->thick, ratio);
            target.masterAlpha = interpolate(lhs->masterAlpha, rhs->masterAlpha, ratio);

            return ret;
*/
        }
};


/// Use as the top level merge function. It stores a map of all other
/// merge functions, using the function that fits best with the
/// incoming type parameters.
class MasterRenderItemMerge : public RenderItemMerge<RenderItem> {

typedef std::map<TypeIdPair, RenderItemMergeFunction*> MergeFunctionMap;
public:

	MasterRenderItemMerge() {}
	virtual ~MasterRenderItemMerge() {}

	inline void add(RenderItemMergeFunction * fun) {
		_mergeFunctionMap[fun->typeIdPair()] = fun;
	}

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

		RenderItemMergeFunction * mergeFunction;

		TypeIdPair pair(typeid(lhs), typeid(rhs));
		if (_mergeFunctionMap.count(pair)) {
			mergeFunction = _mergeFunctionMap[pair];
		} else if (_mergeFunctionMap.count(pair = TypeIdPair(typeid(rhs), typeid(lhs)))) {
			mergeFunction = _mergeFunctionMap[pair];
		} else {
			mergeFunction  = 0;
		}

		// If specialized mergeFunction exists, use it to get higher granularity
		// of correctness
		if (mergeFunction)
			return (*mergeFunction)(lhs, rhs, ratio);
		else
			return 0;
	}

private:
	mutable MergeFunctionMap _mergeFunctionMap;
};

#endif /* RenderItemMergeFunction_HPP_ */