#pragma once
#include <dsp/common.hpp>


namespace rack {
namespace dsp {


/** Detects when a boolean changes from false to true */
struct BooleanTrigger {
	enum State : uint8_t {
		LOW,
		HIGH,
		UNINITIALIZED
	};
	union {
		State s = UNINITIALIZED;
		/** Deprecated */
		bool state;
	};

	void reset() {
		s = UNINITIALIZED;
	}

	/** Returns whether the input changed from false to true. */
	bool process(bool in) {
		bool triggered = (s == LOW) && in;
		s = in ? HIGH : LOW;
		return triggered;
	}

	enum Event {
		NONE = 0,
		TRIGGERED = 1,
		UNTRIGGERED = -1
	};
	/** Returns TRIGGERED if the input changed from false to true, and UNTRIGGERED if the input changed from true to false.
	*/
	Event processEvent(bool in) {
		Event event = NONE;
		if (s == LOW && in) {
			event = TRIGGERED;
		}
		else if (s == HIGH && !in) {
			event = UNTRIGGERED;
		}
		s = in ? HIGH : LOW;
		return event;
	}

	bool isHigh() {
		return s == HIGH;
	}
};


/** Turns HIGH when value reaches a threshold (default 0.f), turns LOW when value reaches a threshold (default 1.f).
*/
template <typename T = float>
struct TSchmittTrigger {
	T state;
	TSchmittTrigger() {
		reset();
	}
	void reset() {
		state = T::mask();
	}
	T process(T in, T lowThreshold = 0.f, T highThreshold = 1.f) {
		T on = (in >= highThreshold);
		T off = (in <= lowThreshold);
		T triggered = ~state & on;
		state = on | (state & ~off);
		return triggered;
	}
	T isHigh() {
		return state;
	}
};


template <>
struct TSchmittTrigger<float> {
	enum State : uint8_t {
		LOW,
		HIGH,
		UNINITIALIZED
	};
	union {
		State s = UNINITIALIZED;
		/** Deprecated. Backward compatible API */
		bool state;
	};

	void reset() {
		s = UNINITIALIZED;
	}

	/** Updates the state of the Schmitt Trigger given a value.
	Returns true if triggered, i.e. the value increases from 0 to 1.
	If different trigger thresholds are needed, use

		process(in, 0.1f, 2.f)

	for example.
	*/
	bool process(float in, float lowThreshold = 0.f, float highThreshold = 1.f) {
		if (s == LOW && in >= highThreshold) {
			// LOW to HIGH
			s = HIGH;
			return true;
		}
		else if (s == HIGH && in <= lowThreshold) {
			// HIGH to LOW
			s = LOW;
		}
		else if (s == UNINITIALIZED && in >= highThreshold) {
			// UNINITIALIZED to HIGH
			s = HIGH;
		}
		else if (s == UNINITIALIZED && in <= lowThreshold) {
			// UNINITIALIZED to LOW
			s = LOW;
		}
		return false;
	}

	enum Event {
		NONE = 0,
		TRIGGERED = 1,
		UNTRIGGERED = -1
	};
	/** Returns TRIGGERED if the input reached highThreshold, and UNTRIGGERED if the input reached lowThreshold.
	*/
	Event processEvent(float in, float lowThreshold = 0.f, float highThreshold = 1.f) {
		Event event = NONE;
		if (s == LOW && in >= highThreshold) {
			// LOW to HIGH
			s = HIGH;
			event = TRIGGERED;
		}
		else if (s == HIGH && in <= lowThreshold) {
			// HIGH to LOW
			s = LOW;
			event = UNTRIGGERED;
		}
		else if (s == UNINITIALIZED && in >= highThreshold) {
			// UNINITIALIZED to HIGH
			s = HIGH;
		}
		else if (s == UNINITIALIZED && in <= lowThreshold) {
			// UNINITIALIZED to LOW
			s = LOW;
		}
		return event;
	}

	bool isHigh() {
		return s == HIGH;
	}
};

typedef TSchmittTrigger<> SchmittTrigger;


/** When triggered, holds a high value for a specified time before going low again */
struct PulseGenerator {
	float remaining = 0.f;

	/** Immediately disables the pulse */
	void reset() {
		remaining = 0.f;
	}

	/** Advances the state by `deltaTime`. Returns whether the pulse was in the HIGH state before stepping. */
	bool process(float deltaTime) {
		if (remaining > 0.f) {
			remaining -= deltaTime;
			return true;
		}
		return false;
	}

	bool isHigh() {
		return (remaining > 0.f);
	}

	/** Begins a trigger with the given `duration`. */
	void trigger(float duration = 1e-3f) {
		// Keep the previous pulse if the existing pulse will be held longer than the currently requested one.
		if (duration > remaining) {
			remaining = duration;
		}
	}
};


/** Accumulates a timer when process() is called. */
template <typename T = float>
struct TTimer {
	T time = 0.f;

	void reset() {
		time = 0.f;
	}

	/** Returns the time since last reset or initialization. */
	T process(T deltaTime) {
		time += deltaTime;
		return time;
	}

	T getTime() {
		return time;
	}
};

typedef TTimer<> Timer;


/** Counts calls to process(), returning true every `division` calls.
Example:

	if (divider.process()) {
		// Runs every `division` calls
	}
*/
struct ClockDivider {
	uint32_t clock = 0;
	uint32_t division = 1;

	void reset() {
		clock = 0;
	}

	void setDivision(uint32_t division) {
		this->division = division;
	}

	uint32_t getDivision() {
		return division;
	}

	uint32_t getClock() {
		return clock;
	}

	/** Returns true when the clock reaches `division` and resets. */
	bool process() {
		clock++;
		if (clock >= division) {
			clock = 0;
			return true;
		}
		return false;
	}
};


} // namespace dsp
} // namespace rack
