text: #pragma once
  
  // Qt includes
  #include <QThread>
  
  // util includes
  #include <utils/PixelFormat.h>
  #include <utils/ImageResampler.h>
  
  // Determine the cmake options
  #include <HyperionConfig.h>
  
  // Turbo JPEG decoder
  #ifdef HAVE_TURBO_JPEG
  	#include <turbojpeg.h>
  #endif
  
  /// Encoder thread for USB devices
  class EncoderThread : public QObject
  {
  	Q_OBJECT
  public:
  	explicit EncoderThread();
  	~EncoderThread();
  
  	void setup(
  		PixelFormat pixelFormat, uint8_t* sharedData,
  		int size, int width, int height, int lineLength,
  		unsigned cropLeft, unsigned cropTop, unsigned cropBottom, unsigned cropRight,
  		VideoMode videoMode, FlipMode flipMode, int pixelDecimation);
  
  	void process();
  
  	bool isBusy() { return _busy; }
  	QAtomicInt _busy = false;
  
  signals:
  	void newFrame(const Image<ColorRgb>& data);
  
  private:
  	PixelFormat			_pixelFormat;
  	uint8_t*			_localData,
  						*_flipBuffer;
  	int					_scalingFactorsCount,
  						_width,
  						_height,
  						_lineLength,
  						_currentFrame,
  						_pixelDecimation;
  	unsigned long		_size;
  	unsigned			_cropLeft,
  						_cropTop,
  						_cropBottom,
  						_cropRight;
  	FlipMode			_flipMode;
  	ImageResampler		_imageResampler;
  
  #ifdef HAVE_TURBO_JPEG
  	tjhandle			_transform, _decompress;
  	tjscalingfactor*	_scalingFactors;
  	tjtransform*		_xform;
  
  	void processImageMjpeg();
  #endif
  };
  
  template <typename TThread> class Thread : public QThread
  {
  public:
  	TThread *_thread;
  	explicit Thread(TThread *thread, QObject *parent = nullptr)
  		: QThread(parent)
  		, _thread(thread)
  	{
  		_thread->moveToThread(this);
  		start();
  	}
  
  	~Thread()
  	{
  		quit();
  		wait();
  	}
  
  	EncoderThread* thread() const { return qobject_cast<EncoderThread*>(_thread); }
  
  	void setup(
  		PixelFormat pixelFormat, uint8_t* sharedData,
  		int size, int width, int height, int lineLength,
  		unsigned cropLeft, unsigned cropTop, unsigned cropBottom, unsigned cropRight,
  		VideoMode videoMode, FlipMode flipMode, int pixelDecimation)
  	{
  		auto encThread = qobject_cast<EncoderThread*>(_thread);
  		if (encThread != nullptr)
  			encThread->setup(pixelFormat, sharedData,
  				size, width, height, lineLength,
  				cropLeft, cropTop, cropBottom, cropRight,
  				videoMode, flipMode, pixelDecimation);
  	}
  
  	bool isBusy()
  	{
  		auto encThread = qobject_cast<EncoderThread*>(_thread);
  		if (encThread != nullptr)
  			return encThread->isBusy();
  
  		return true;
  	}
  
  	void process()
  	{
  		auto encThread = qobject_cast<EncoderThread*>(_thread);
  		if (encThread != nullptr)
  			encThread->process();
  	}
  
  protected:
  	void run() override
  	{
  		QThread::run();
  		delete _thread;
  	}
  };
  
  class EncoderThreadManager : public QObject
  {
      Q_OBJECT
  public:
  	explicit EncoderThreadManager(QObject *parent = nullptr)
  		: QObject(parent)
  		, _threadCount(qMax(QThread::idealThreadCount(), 1))
  		, _threads(nullptr)
  	{
  		_threads = new Thread<EncoderThread>*[_threadCount];
  		for (int i = 0; i < _threadCount; i++)
  		{
  			_threads[i] = new Thread<EncoderThread>(new EncoderThread, this);
  			_threads[i]->setObjectName("Encoder " + QString::number(i));
  		}
  	}
  
  	~EncoderThreadManager()
  	{
  		if (_threads != nullptr)
  		{
  			for(int i = 0; i < _threadCount; i++)
  			{
  				_threads[i]->deleteLater();
  				_threads[i] = nullptr;
  			}
  
  			delete[] _threads;
  			_threads = nullptr;
  		}
  	}
  
  	void start()
  	{
  		if (_threads != nullptr)
  			for (int i = 0; i < _threadCount; i++)
  				connect(_threads[i]->thread(), &EncoderThread::newFrame, this, &EncoderThreadManager::newFrame);
  	}
  
  	void stop()
  	{
  		if (_threads != nullptr)
  			for(int i = 0; i < _threadCount; i++)
  				disconnect(_threads[i]->thread(), nullptr, nullptr, nullptr);
  	}
  
  	int					_threadCount;
  	Thread<EncoderThread>**	_threads;
  
  signals:
  	void newFrame(const Image<ColorRgb>& data);
  };
  