#pragma once

#include <string>
#include <vector>
#include <unordered_map>
#include <memory>
#include <utility>
#include <random>
#include <rack.hpp>
#include <cstdint>
#include <array>
#include "core/timeseq-validation.hpp"

#ifndef nt_private
	#define nt_private private
#endif


namespace timeseq {

struct ScriptIf;
struct ScriptTimeline;
struct ScriptClockLane;
struct ScriptLane;
struct ScriptSegment;
struct ScriptCalc;
struct ScriptTuning;
struct Script;
struct ValueProcessor;
struct PortHandler;
struct VariableHandler;
struct TriggerHandler;
struct EventListener;
struct AssertListener;


struct CalcProcessor {
	virtual double calc(double value) = 0;
};

struct CalcValueProcessor : CalcProcessor {
	enum ValueCalcOperation { ADD, SUB, DIV, MULT, MAX, MIN, REMAIN };

	CalcValueProcessor(const ScriptCalc* scriptCalc, const std::shared_ptr<ValueProcessor>& value);

	double calc(double value) override;

	nt_private:
		ValueCalcOperation m_operation;
		const std::shared_ptr<ValueProcessor> m_value;
};

struct CalcTruncProcessor : CalcProcessor {
	double calc(double value) override;
};

struct CalcFracProcessor : CalcProcessor {
	double calc(double value) override;
};

struct CalcRoundProcessor : CalcProcessor {
	CalcRoundProcessor(const ScriptCalc* scriptCalc);

	double calc(double value) override;

	nt_private:
		const ScriptCalc* m_scriptCalc;
};

struct CalcQuantizeProcessor : CalcProcessor {
	CalcQuantizeProcessor(const ScriptTuning* scriptTuning);

	double calc(double value) override;

	nt_private:
		std::vector<std::array<float, 2>> m_quantizeValues;
};

struct CalcSignProcessor : CalcProcessor {
	CalcSignProcessor(const ScriptCalc* scriptCalc);

	double calc(double value) override;

	nt_private:
		bool m_positive;
};

struct CalcVtoFProcessor : CalcProcessor {
	double calc(double value) override;
};

struct RandValueGenerator {
	RandValueGenerator();
	virtual ~RandValueGenerator();

	virtual float generate(float lower, float upper);

	nt_private:
		std::minstd_rand m_generator;
};

struct SequenceProcessor {
	SequenceProcessor(const std::string& id, const std::vector<std::shared_ptr<ValueProcessor>>& values, bool retrieveVoltageOnce);

	std::string getId();
	const std::vector<std::shared_ptr<ValueProcessor>> getValues() const;
	bool isRetrieveVoltageOnce();

	void clear();
	void add(const std::shared_ptr<ValueProcessor>& value, int position);
	void remove(int position);

	nt_private:
		std::string m_id;
		std::vector<std::shared_ptr<ValueProcessor>> m_values;
		bool m_retrieveVoltageOnce;
};

struct SequencePositionProcessor {
	enum SequenceMoveDirection { FORWARD, BACKWARD, RANDOM, NONE };

	SequencePositionProcessor(const std::shared_ptr<SequenceProcessor>& sequenceProcessor, const std::shared_ptr<RandValueGenerator>& randValueGenerator);

	SequenceProcessor* getSequenceProcessor();
	double getCurrentValue();

	void move(SequenceMoveDirection direction, bool wrap);
	void move(int position);

	nt_private:
		int m_position;
		const std::shared_ptr<SequenceProcessor> m_sequenceProcessor;
		const std::shared_ptr<RandValueGenerator> m_randValueGenerator;

		// If the sequence has 'retrieve-voltage-once' set to true, we need to store the voltage
		// and remember it until a move is done.
		bool m_hasStoredVoltage;
		double m_storedVoltage;
};

struct ValueProcessor {
	ValueProcessor(const std::vector<std::shared_ptr<CalcProcessor>>& calcProcessors, bool quantize);

	double process();
	virtual double processValue() = 0;

	nt_private:
		const std::vector<std::shared_ptr<CalcProcessor>> m_calcProcessors;
		bool m_quantize;

		double quantize(double value);
};

struct StaticValueProcessor : ValueProcessor {
	StaticValueProcessor(float value, const std::vector<std::shared_ptr<CalcProcessor>>& calcProcessors, bool quantize);

	double processValue() override;

	nt_private:
		float m_value;
};

struct VariableValueProcessor : ValueProcessor {
	VariableValueProcessor(const std::string& name, const std::vector<std::shared_ptr<CalcProcessor>>& calcProcessors, bool quantize, VariableHandler* variableHandler);

	double processValue() override;

	nt_private:
		const std::string m_name;
		VariableHandler* m_variableHandler;
};

struct InputValueProcessor : ValueProcessor {
	InputValueProcessor(int inputPort, int inputChannel, const std::vector<std::shared_ptr<CalcProcessor>>& calcProcessors, bool quantize, PortHandler* portHandler);

	double processValue() override;

	nt_private:
		int m_inputPort;
		int m_inputChannel;
		PortHandler* m_portHandler;
};

struct OutputValueProcessor : ValueProcessor {
	OutputValueProcessor(int outputPort, int outputChannel, const std::vector<std::shared_ptr<CalcProcessor>>& calcProcessors, bool quantize, PortHandler* portHandler);

	double processValue() override;

	nt_private:
		int m_outputPort;
		int m_outputChannel;
		PortHandler* m_portHandler;
};

struct RandValueProcessor : ValueProcessor {
	RandValueProcessor(const std::shared_ptr<ValueProcessor>& lowerValue, const std::shared_ptr<ValueProcessor>& upperValue, const std::shared_ptr<RandValueGenerator>& randValueGenerator, const std::vector<std::shared_ptr<CalcProcessor>>& calcProcessors, bool quantize);

	double processValue() override;

	nt_private:
		const std::shared_ptr<ValueProcessor> m_lowerValue;
		const std::shared_ptr<ValueProcessor> m_upperValue;

		const std::shared_ptr<RandValueGenerator> m_randValueGenerator;
};

struct SequenceValueProcessor : ValueProcessor {
	SequenceValueProcessor(const std::shared_ptr<SequencePositionProcessor>& sequencePositionProcessor, SequencePositionProcessor::SequenceMoveDirection moveBefore, SequencePositionProcessor::SequenceMoveDirection moveAfter, bool wrap, const std::vector<std::shared_ptr<CalcProcessor>>& calcProcessors, bool quantize);

	double processValue() override;

	nt_private:
		const std::shared_ptr<SequencePositionProcessor> m_sequencePositionProcessor;
		SequencePositionProcessor::SequenceMoveDirection m_moveBefore;
		SequencePositionProcessor::SequenceMoveDirection m_moveAfter;
		bool m_wrap;
};

struct IfProcessor {
	IfProcessor(const ScriptIf* scriptIf, const std::pair<const std::shared_ptr<ValueProcessor>, const std::shared_ptr<ValueProcessor>>& values, const std::vector<std::shared_ptr<IfProcessor>>& ifs);

	bool process(std::string* message);

	nt_private:
		const ScriptIf* m_scriptIf;
		const std::pair<const std::shared_ptr<ValueProcessor>, const std::shared_ptr<ValueProcessor>> m_values;
		const std::vector<std::shared_ptr<IfProcessor>> m_ifs;
};

struct ActionProcessor {
	ActionProcessor(const std::shared_ptr<IfProcessor>& ifProcessor);

	void process();
	virtual void processAction() = 0;

	nt_private:
		const std::shared_ptr<IfProcessor> m_ifProcessor;
};

struct ActionSetValueProcessor : ActionProcessor {
	ActionSetValueProcessor(const std::shared_ptr<ValueProcessor>& value, int outputPort, int outputChannel, PortHandler* portHandler, const std::shared_ptr<IfProcessor>& ifProcessor);

	void processAction() override;

	nt_private:
		const std::shared_ptr<ValueProcessor> m_value;
		int m_outputPort;
		int m_outputChannel;
		PortHandler* m_portHandler;
};

struct ActionSetVariableProcessor : ActionProcessor {
	ActionSetVariableProcessor(const std::shared_ptr<ValueProcessor>& value, const std::string& name, VariableHandler* variableHandler, const std::shared_ptr<IfProcessor>& ifProcessor);

	void processAction() override;

	nt_private:
		const std::shared_ptr<ValueProcessor> m_value;
		const std::string m_name;
		VariableHandler* m_variableHandler;
};

struct ActionSetPolyphonyProcessor : ActionProcessor {
	ActionSetPolyphonyProcessor(int outputPort, int channelCount, PortHandler* portHandler, const std::shared_ptr<IfProcessor>& ifProcessor);

	void processAction() override;

	nt_private:
		int m_outputPort;
		int m_channelCount;
		PortHandler* m_portHandler;
};

struct ActionSetLabelProcessor : ActionProcessor {
	ActionSetLabelProcessor(int outputPort, const std::string& label, PortHandler* portHandler, const std::shared_ptr<IfProcessor>& ifProcessor);

	void processAction() override;

	nt_private:
		int m_outputPort;
		const std::string m_label;
		PortHandler* m_portHandler;
};

struct ActionAssertProcessor : ActionProcessor {
	ActionAssertProcessor(const std::string& name, const std::shared_ptr<IfProcessor>& expect, bool stopOnFail, AssertListener* assertListener, const std::shared_ptr<IfProcessor>& ifProcessor);

	void processAction() override;

	nt_private:
		const std::string m_name;
		const std::shared_ptr<IfProcessor> m_expect;
		bool m_stopOnFail;
		AssertListener* m_assertListener;
};

struct ActionTriggerProcessor : ActionProcessor {
	ActionTriggerProcessor(const std::string& trigger, TriggerHandler* triggerHandler, const std::shared_ptr<IfProcessor>& ifProcessor);

	void processAction() override;

	nt_private:
		const std::string m_trigger;
		TriggerHandler* m_triggerHandler;
};

struct ActionMoveSequenceDirectionProcessor : ActionProcessor {
	ActionMoveSequenceDirectionProcessor(std::shared_ptr<SequencePositionProcessor>& sequencePositionProcessor, SequencePositionProcessor::SequenceMoveDirection direction, bool wrap, const std::shared_ptr<IfProcessor>& ifProcessor);

	void processAction() override;

	nt_private:
		std::shared_ptr<SequencePositionProcessor> m_sequencePositionProcessor;
		SequencePositionProcessor::SequenceMoveDirection m_direction;
		bool m_wrap;
};

struct ActionMoveSequencePositionProcessor : ActionProcessor {
	ActionMoveSequencePositionProcessor(std::shared_ptr<SequencePositionProcessor>& sequencePositionProcessor, int position, const std::shared_ptr<IfProcessor>& ifProcessor);

	void processAction() override;

	nt_private:
		std::shared_ptr<SequencePositionProcessor> m_sequencePositionProcessor;
		int m_position;
};

struct ActionClearSequenceProcessor : ActionProcessor {
	ActionClearSequenceProcessor(std::shared_ptr<SequencePositionProcessor>& sequencePositionProcessor, const std::shared_ptr<IfProcessor>& ifProcessor);

	void processAction() override;

	nt_private:
		std::shared_ptr<SequencePositionProcessor> m_sequencePositionProcessor;
};

struct ActionAddToSequenceSequenceProcessor : ActionProcessor {
	ActionAddToSequenceSequenceProcessor(std::shared_ptr<SequencePositionProcessor>& sequencePositionProcessor, const std::shared_ptr<ValueProcessor>& value, int position, bool asConstantVoltage, const std::shared_ptr<IfProcessor>& ifProcessor);

	void processAction() override;

	nt_private:
		std::shared_ptr<SequencePositionProcessor> m_sequencePositionProcessor;
		const std::shared_ptr<ValueProcessor> m_value;
		int m_position;
		bool m_asConstantVoltage;
};

struct ActionRemoveFromSequenceProcessor : ActionProcessor {
	ActionRemoveFromSequenceProcessor(std::shared_ptr<SequencePositionProcessor>& sequencePositionProcessor, int position, const std::shared_ptr<IfProcessor>& ifProcessor);

	void processAction() override;

	nt_private:
		std::shared_ptr<SequencePositionProcessor> m_sequencePositionProcessor;
		int m_position;
};

struct ActionOngoingProcessor {
	ActionOngoingProcessor(const std::shared_ptr<IfProcessor>& ifProcessor);

	virtual void start(uint64_t glideLength);
	virtual void process(uint64_t glidePosition) = 0;
	virtual void end() = 0;

	protected:
		bool shouldProcess();

	nt_private:
		const std::shared_ptr<IfProcessor> m_ifProcessor;

		// The result of the "if" validation as captured when the ongoing action was started
		bool m_if;
};

struct ActionGlideProcessor : ActionOngoingProcessor {
	ActionGlideProcessor(float easeFactor, bool easePow, const std::shared_ptr<ValueProcessor>& startValue, const std::shared_ptr<ValueProcessor>& endValue, const std::shared_ptr<IfProcessor>& ifProcessor, int outputPort, int outputChannel, const std::string& variable, PortHandler* portHandler, VariableHandler* variableHandler);

	void start(uint64_t glideLength) override;
	void process(uint64_t glidePosition) override;
	void end() override;

	nt_private:
		float m_easeFactor;
		bool m_easePow;
		const std::shared_ptr<ValueProcessor> m_startValueProcessor;
		const std::shared_ptr<ValueProcessor> m_endValueProcessor;

		PortHandler* m_portHandler;
		VariableHandler* m_variableHandler;

		int m_outputPort;
		int m_outputChannel;
		const std::string m_variable;

		// The start and end values that were captured when the glide action was started
		double m_startValue;
		double m_endValue;
		// Pre-calculated for runtime performance: the difference between start and end, and "1.0 / duration"
		double m_valueDelta;
		double m_durationInverse;

		// A power-based easing calculation
		double calculatePowEase(float ease);
		// A sigmoid-based easing calculation
		double calculateSigEase(float ease);
};

struct DurationProcessor {
	enum DurationState { STATE_START, STATE_PROGRESS, STATE_END };

	virtual void prepareForStart() = 0;

	DurationState getState();
	uint64_t getPosition();
	uint64_t getDuration();

	double process(double drift);
	void reset();

	void setDuration(uint64_t duration);
	void setDrift(double drift);

	nt_private:
		DurationState m_state = STATE_START;
		uint64_t m_duration;
		double m_drift;
		uint64_t m_position;
};

struct DurationConstantProcessor : DurationProcessor {
	DurationConstantProcessor(uint64_t duration, double drift);

	void prepareForStart() override;
};

struct DurationVariableFactorProcessor : DurationProcessor {
	DurationVariableFactorProcessor(const std::shared_ptr<ValueProcessor>& value, double samplesFactor);

	void prepareForStart() override;

	nt_private:
		const std::shared_ptr<ValueProcessor> m_value;
		double m_samplesFactor;
};

struct DurationVariableHzProcessor : DurationProcessor {
	DurationVariableHzProcessor(const std::shared_ptr<ValueProcessor>& value, double sampleRate);

	void prepareForStart() override;

	nt_private:
		const std::shared_ptr<ValueProcessor> m_value;
		double m_sampleRate;
};

struct ActionGateProcessor : ActionOngoingProcessor {
	ActionGateProcessor(float gateHighRatio, const std::shared_ptr<DurationProcessor> gateHighDuration, const std::shared_ptr<IfProcessor>& ifProcessor, int outputPort, int outputChannel, float sampleRate, PortHandler* portHandler);

	void start(uint64_t glideLength) override;
	void process(uint64_t glidePosition) override;
	void end() override;

	nt_private:
		PortHandler* m_portHandler;

		int m_outputPort;
		int m_outputChannel;
		float m_gateHighRatio;
		std::shared_ptr<DurationProcessor> m_gateHighDuration;

		bool m_gateHigh;
		uint64_t m_gateLowPosition;

		double m_samplesIn2Ms;
};

struct SegmentProcessor {
	SegmentProcessor(const SegmentProcessor& segmentProcessor);
	SegmentProcessor(
		const std::shared_ptr<DurationProcessor>& durationProcessor,
		const std::vector<std::shared_ptr<ActionProcessor>>& startActions,
		const std::vector<std::shared_ptr<ActionProcessor>>& endActions,
		const std::vector<std::shared_ptr<ActionOngoingProcessor>>& ongoingActions,
		bool disableUi,
		EventListener* eventListener
	);

	void pushStartActions(const std::vector<std::shared_ptr<ActionProcessor>>& startActions);
	void pushEndActions(const std::vector<std::shared_ptr<ActionProcessor>>& endActions);

	DurationProcessor::DurationState getState();

	double process(double drift);
	void reset();

	nt_private:
		const std::shared_ptr<DurationProcessor> m_duration;

		std::vector<std::shared_ptr<ActionProcessor>> m_startActions;
		std::vector<std::shared_ptr<ActionProcessor>> m_endActions;
		const std::vector<std::shared_ptr<ActionOngoingProcessor>> m_ongoingActions;

		bool m_disableUi;

		EventListener* m_eventListener;

		void processStartActions();
		void processEndActions();
		void processOngoingActions(bool start, bool end);
};

struct LaneProcessor {
	enum LaneState { STATE_IDLE, STATE_PROCESSING, STATE_PENDING_LOOP };

	LaneProcessor(const ScriptLane* scriptLane, const std::vector<std::shared_ptr<SegmentProcessor>>& segments, EventListener* eventListener);
	LaneProcessor(const ScriptClockLane* scriptClockLane, const std::vector<std::shared_ptr<SegmentProcessor>>& segments, EventListener* eventListener);

	LaneState getState();

	bool process();
	void loop();
	void reset();

	void processTriggers(const std::vector<std::string>& triggers);

	nt_private:
		bool m_autoStart;
		bool m_loop;
		int m_repeat;

		std::string m_startTrigger;
		std::string m_stopTrigger;
		std::string m_restartTrigger;

		bool m_disableUi;
		const std::vector<std::shared_ptr<SegmentProcessor>> m_segments;

		LaneState m_state = LaneState::STATE_IDLE;
		int m_repeatCount = 0;

		int m_activeSegment = 0;
		double m_drift = 0.;

		EventListener* m_eventListener;
	};

struct TimelineProcessor {
	TimelineProcessor(
		bool loopLock,
		const std::vector<std::shared_ptr<LaneProcessor>>& lanes,
		TriggerHandler* triggerHandler
	);

	void process();
	void reset();

	nt_private:
		bool m_loopLock;
		const std::vector<std::shared_ptr<LaneProcessor>> m_lanes;

		TriggerHandler* m_triggerHandler;
};

struct TriggerProcessor {
	TriggerProcessor(const std::string& id, int inputPort, int inputChannel, PortHandler* portHandler, TriggerHandler* triggerHandler);

	void process();

	nt_private:
		const std::string m_id;
		int m_inputPort;
		int m_inputChannel;
		PortHandler* m_portHandler;
		TriggerHandler* m_triggerHandler;

		rack::dsp::TSchmittTrigger<float> m_trigger;
};

struct Processor {
	Processor(const std::shared_ptr<Script>& script, const std::vector<std::shared_ptr<TimelineProcessor>>& timelines, const std::vector<std::shared_ptr<TriggerProcessor>>& triggers, const std::vector<std::shared_ptr<ActionProcessor>>& startActions);

	virtual void reset();
	virtual void process();

	nt_private:
		const std::vector<std::shared_ptr<TimelineProcessor>> m_timelines;
		const std::vector<std::shared_ptr<TriggerProcessor>> m_triggers;
		const std::vector<std::shared_ptr<ActionProcessor>> m_startActions;

		// The script objects are used throughout the processor hierarchy,
		// keep a reference so it doesn't get deleted on script switch
		const std::shared_ptr<Script> m_script;
};

}
