#pragma once

// STL includes
#include <vector>
#include <cstdint>

// QT includes
#include <QMap>
#include <QObject>
#include <QMap>
#include <QVector>

// Utils includes
#include <utils/ColorRgb.h>
#include <utils/Image.h>
#include <utils/Components.h>

// global defines
#define SMOOTHING_MODE_DEFAULT 0
#define SMOOTHING_MODE_PAUSE   1

class QTimer;
class Logger;

///
/// The PriorityMuxer handles the priority channels. Led values input/ images are written to the priority map
/// and the muxer keeps track of all active priorities. The current priority can be queried and per
/// priority the led colors. Handles also manual/auto selection mode, provides a lot of signals to hook into priority related events
///
class PriorityMuxer : public QObject
{
	Q_OBJECT
public:
	///
	/// The information structure for a single priority channel
	///
	struct InputInfo
	{
		/// The priority of this channel
		int priority;
		/// The absolute timeout of the channel
		int64_t timeoutTime_ms;
		/// The colors for each led of the channel
		std::vector<ColorRgb> ledColors;
		/// The raw Image (size should be preprocessed!)
		Image<ColorRgb> image;
		/// The component
		hyperion::Components componentId;
		/// Who set it
		QString origin;
		/// id of smoothing config
		unsigned smooth_cfg;
		/// specific owner description
		QString owner;
	};

	//Foreground and Background priorities
	const static int FG_PRIORITY;
	const static int BG_PRIORITY;
	const static int MANUAL_SELECTED_PRIORITY;
	/// The lowest possible priority, which is used when no priority channels are active
	const static int LOWEST_PRIORITY;
	/// Timeout used to identify a non active priority
	const static int TIMEOUT_NOT_ACTIVE_PRIO;

	///
	/// Constructs the PriorityMuxer for the given number of LEDs (used to switch to black when
	/// there are no priority channels
	///
	/// @param ledCount The number of LEDs
	///
	PriorityMuxer(int ledCount, QObject * parent);

	///
	/// Destructor
	///
	~PriorityMuxer() override;

	///
	/// @brief Start/Stop the PriorityMuxer update timer; On disabled no priority and timeout updates will be performend
	/// @param  enable  The new state
	///
	void setEnable(bool enable);

	/// @brief Enable or disable auto source selection
	/// @param   enable   True if it should be enabled else false
	/// @param   update   True to update _currentPriority - INTERNAL usage.
	/// @return           True if changed has been applied, false if the state is unchanged
	///
	bool setSourceAutoSelectEnabled(bool enable, bool update = true);

	///
	/// @brief Get the state of source auto selection
	/// @return  True if enabled, else false
	///
	bool isSourceAutoSelectEnabled() const { return _sourceAutoSelectEnabled; }

	///
	/// @brief  Overwrite current lowest priority with manual selection; On success disables auto selection
	/// @param   priority  The
	/// @return            True on success, false if priority not found
	///
	bool setPriority(int priority);

	///
	/// @brief Update all LED-Colors with min length of >= 1 to fit the new led length
	/// @param[in] ledCount   The count of LEDs
	///
	void updateLedColorsLength(int ledCount);

	///
	/// Returns the current priority
	///
	/// @return The current priority
	///
	int getCurrentPriority() const { return _currentPriority; }

	///
	/// Returns the previous priority before current priority
	///
	/// @return The previous priority
	///
	int getPreviousPriority() const { return _previousPriority; }

	///
	/// Returns the state (enabled/disabled) of a specific priority channel
	/// @param priority The priority channel
	/// @return True if the priority channel exists else false
	///
	bool hasPriority(int priority) const;

	///
	/// Returns the number of active priorities
	///
	/// @return The list with active priorities
	///
	QList<int> getPriorities() const;

	///
	/// Returns the information of a specified priority channel.
	/// If a priority is no longer available the _lowestPriorityInfo (255) is returned
	///
	/// @param priority The priority channel
	///
	/// @return The information for the specified priority channel
	///
	InputInfo getInputInfo(int priority) const;

	///
	/// @brief  Register a new input by priority, the priority is not active (timeout -100 isn't muxer recognized) until you start to update the data with setInput()
	/// 		A repeated call to update the base data of a known priority won't overwrite their current timeout
	/// @param[in] priority    The priority of the channel
	/// @param[in] component   The component of the channel
	/// @param[in] origin      Who set the channel (CustomString@IP)
	/// @param[in] owner       Specific owner string, might be empty
	/// @param[in] smooth_cfg  The smooth id to use
	///
	void registerInput(int priority, hyperion::Components component, const QString& origin = "System", const QString& owner = "", unsigned smooth_cfg = SMOOTHING_MODE_DEFAULT);

	///
	/// @brief   Update the current color of a priority (previous registered with registerInput())
	/// @param  priority    The priority to update
	/// @param  ledColors   The colors
	/// @param  timeout_ms  The new timeout (defaults to -1 endless)
	/// @return             True on success, false when priority is not found
	///
	bool setInput(int priority, const std::vector<ColorRgb>& ledColors, int64_t timeout_ms = -1);

	///
	/// @brief   Update the current image of a priority (prev registered with registerInput())
	/// @param  priority    The priority to update
	/// @param  image       The new image
	/// @param  timeout_ms  The new timeout (defaults to -1 endless)
	/// @return             True on success, false when priority is not found
	///
	bool setInputImage(int priority, const Image<ColorRgb>& image, int64_t timeout_ms = -1);

	///
	/// @brief Set the given priority to inactive
	/// @param priority  The priority
	/// @return True on success false if not found
	///
	bool setInputInactive(int priority);

	///
	/// Clears the specified priority channel and update _currentPriority on success
	///
	/// @param[in] priority  The priority of the channel to clear
	/// @return              True if priority has been cleared else false (not found)
	///
	bool clearInput(int priority);

	///
	/// Clears all priority channels
	///
	void clearAll(bool forceClearAll=false);

	///
	/// @brief Queue a manual push where muxer doesn't recognize them (e.g. continuous single color pushes)
	///
	void queuePush() { emit timeRunner(); }

signals:
	///
	/// @brief Signal which emits when a effect or color with timeout > -1 is running, once per second
	///
	void timeRunner();

	///
	/// @brief Emits whenever the visible priority has changed
	/// @param  priority  The new visible priority
	///
	void visiblePriorityChanged(quint8 priority);

	///
	/// @brief Emits whenever the current visible component changed
	/// @param comp  The new component
	///
	void visibleComponentChanged(hyperion::Components comp);

	///
	/// @brief Emits whenever something changes which influences the priorities listing
	///        Emits also in 1s interval when a COLOR or EFFECT is running with a timeout > -1
	///
	void prioritiesChanged();

	///
	/// internal used signal to resolve treading issues with timer
	///
	void signalTimeTrigger();

private slots:
	///
	/// Slot which is called to adapt to 1s interval for signal timeRunner() / prioritiesChanged()
	///
	void timeTrigger();

	///
	/// Updates the current time. Channels with a configured time out will be checked and cleared if
	/// required.
	///
	void setCurrentTime();

private:
	///
	/// @brief Get the component of the given priority
	/// @return The component
	///
	hyperion::Components getComponentOfPriority(int priority) const;

	/// Logger instance
	Logger* _log;

	/// The current priority (lowest value in _activeInputs)
	int _currentPriority;

	/// The previous priority before current priority
	int _previousPriority;

	/// The manual select priority set with setPriority
	int _manualSelectedPriority;

	// The last visible component
	hyperion::Components _prevVisComp = hyperion::COMP_INVALID;

	/// The mapping from priority channel to led-information
	QMap<int, InputInfo> _activeInputs;

	/// The information of the lowest priority channel
	InputInfo _lowestPriorityInfo;

	// Reflect the state of auto select
	bool _sourceAutoSelectEnabled;

	// Timer to update Muxer times independent
	QTimer* _updateTimer;

	QTimer* _timer;
	QTimer* _blockTimer;
};
