// Copyright 2004 David Hilvert <dhilvert@auricle.dyndns.org>,
//                              <dhilvert@ugcs.caltech.edu>

/*  This file is part of the Anti-Lamenessing Engine.

    The Anti-Lamenessing Engine is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    The Anti-Lamenessing Engine is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with the Anti-Lamenessing Engine; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/*
 * exposure.h: A superclass for all exposure classes.
 */

#ifndef __exposure_h__
#define __exposure_h__

#include "../pixel.h"

/*
 * This class models a non-linear response function.  More information can be
 * found here:
 *
 * http://wearcam.org/comparametrics.pdf
 */

class exposure {
private:
	static ale_real confidence_exponent;
	pixel _multiplier;
	ale_real _gain_multiplier;
	static ale_real _gain_reference;
	ale_real _black_level;

public:

	/*
	 * confidence/uniform static mutators
	 */
	static void set_confidence(ale_real exponent) {
		confidence_exponent = exponent;
	}

	/*
	 * confidence accessor
	 */
	static ale_real get_confidence() {
		return confidence_exponent;
	}

	/*
	 * Event listener interface
	 */

	class listener {
		friend class exposure;
	private:
		listener *next;
		const char *name;
		const exposure *target;
	public:
		virtual void trigger(pixel multiplier) = 0;

		listener () {
			next = NULL;
			target = NULL;
		}

		virtual ~listener() {
			if (target) {
				target->remove_listener(this);
			}
		}
	};

private:
	mutable listener *listener_head;
public:

	void add_listener(listener *l, const char *name) const {
	
		/*
		 * This is a metafunction, so we consider it
		 * to leave the object constant.
		 */
		assert (l->next == NULL);
		assert (l->target == NULL);
		l->next = listener_head;
		l->target = this;
		l->name = name;
		listener_head = l;
	}

	void remove_listener(listener *l) const {

		assert (listener_head != NULL);

		if (listener_head == l) {
			listener_head = listener_head->next;
		} else {
			assert (listener_head->next != NULL);

			listener *a = listener_head;
			listener *b = listener_head->next;

			while (b != l) {
				assert (b->next != NULL);
				a = b;
				b = b->next;
			}

			a->next = b->next;
		}

		l->target = NULL;
	}
	
	void set_multiplier(pixel _multiplier) {
		listener *cl = listener_head;

		while(cl != NULL) {
			cl->trigger(_multiplier / this->_multiplier);
			cl = cl->next;
		}

		this->_multiplier = _multiplier;
	}

	void set_gain_multiplier(ale_real g) {
		_gain_multiplier = g;
	}

	ale_real get_gain_multiplier() {
		return _gain_multiplier;
	}

	void set_black_level(ale_real b) {
		_black_level = b;
	}

	ale_real get_black_level() {
		return _black_level;
	}

	static void set_gain_reference(ale_real r) {
		_gain_reference = r;
	}

	static ale_real get_gain_reference() {
		return _gain_reference;
	}

	pixel get_multiplier() const {
		return _multiplier;
	}

	virtual ale_real confidence(unsigned int k, ale_real input, 
			ale_real confidence_floor = ale_real_confidence_floor) const {

		ale_real _0 = (ale_real) 0;
		ale_real _4 = (ale_real) 4;
		ale_real _2 = (ale_real) 2;
		ale_real _1 = (ale_real) 1;

		if (confidence_exponent == _0)
			return 1;

		ale_real input_scaled = input / _multiplier[k];

		ale_real unexponentiated = _1 - _4 * ((_1 / _2) - input_scaled)
		                                   * ((_1 / _2) - input_scaled);

// 		ale_real unexponentiated = _4 * ((_1 / _4) - (ale_real) ((_1 / _2) - input_scaled)
// 		                                                      * ((_1 / _2) - input_scaled));

		// ale_real unexponentiated = 4 * input_scaled * (0.25 - pow(0.5 - input_scaled, 2));

		ale_real exponentiated;

		if (confidence_exponent != _1) {

			if (unexponentiated < _0) 
				return confidence_floor;

			exponentiated = pow(unexponentiated, confidence_exponent);

		} else {
			exponentiated = unexponentiated;
		}

		if (exponentiated < confidence_floor || !finite(exponentiated))
			return confidence_floor;

		return exponentiated;
	}

	/*
	 * This is a very hackish confidence function.  It's zero at the
	 * extremes of camera response and maximal at the center.
	 */
	virtual pixel confidence(pixel input, 
			ale_real confidence_floor = ale_real_confidence_floor) const {

		if (confidence_exponent != 0) {
			return pixel(confidence(0, input[0], confidence_floor),
				     confidence(1, input[1], confidence_floor),
				     confidence(2, input[2], confidence_floor));
		} else {
			return pixel(1, 1, 1);
		}
	}

	/*
	 * Confidence that the real value is lower or higher than the given
	 * value.  
	 *
	 * XXX: This function now applies the one-sided condition only to
	 * responses greater than 50%.  Should it be called
	 * 'upper_one_sided_confidence' instead?
	 */
	virtual pixel one_sided_confidence(pixel input, pixel sign) const {
		if (confidence_exponent != 0) {
			pixel result = confidence(input);
			for (unsigned int k = 0; k < 3; k++) {
				if (sign[k] > 0 && input[k] / _multiplier[k] > 1 / (ale_real) 2)
					result[k] = 1;
			}
			return result;
		} else {
			return pixel(1, 1, 1);
		}
	}

	virtual pixel linearize(pixel input) const = 0;
	virtual pixel unlinearize(pixel input) const = 0;

	exposure() {
		listener_head = NULL;
		_multiplier = pixel(1, 1, 1);
		_gain_multiplier = 1;
		_black_level = 0;
	}

	virtual ~exposure() {
	}
};

#endif
