#pragma once

#include "ovas_base.h"
#include "ovasIDriver.h"
#include "ovasIHeader.h"
#include "ovasCDriftCorrection.h"

#include <socket/IConnectionServer.h>

#include <mutex>
#include <thread>

#include <vector>
#include <list>
#include <deque>

namespace OpenViBE {
namespace AcquisitionServer {
class CConnectionServerHandlerThread;
class CConnectionClientHandlerThread;
class IAcquisitionServerPlugin;

typedef struct
{
	uint64_t connectionTime;										// Time the client connected
	uint64_t stimulationTimeOffset;									// Time offset wrt acquisition start
	uint64_t nSampleToSkip;											// How many samples to skip wrt current buffer start. n.b. not a constant.
	CConnectionClientHandlerThread* connectionClientHandlerThread;	// Ptr to the class object that is executed by the client connection handler thread
	std::thread* connectionClientHandlerStdThread;					// The actual thread handle
	bool isChannelUnitsSent;
	bool isChannelLocalisationSent;
} connection_info_t;

enum class ENaNReplacementPolicy { LastCorrectValue=0, Zero, Disabled };

std::string toString(const ENaNReplacementPolicy policy);

// Concurrency handling
class DoubleLock
{
	// Implements
	//   lock(mutex1);
	//   lock(mutex2);
	//   unlock(mutex1);
	// mutex2 lock is released when the object goes out of scope
	//
	// @details The two mutex 'pattern' is used to avoid thread starving which can happen e.g.
	// on Linux if just a single mutex is used; apparently the main loop just takes the 
	// mutex repeatedly without the gui thread sitting on the mutex being unlocked.
	// n.b. potentially calls for redesign
public:
	DoubleLock(std::mutex* m1, std::mutex* m2) : lock2(*m2, std::defer_lock)
	{
		std::lock_guard<std::mutex> lock1(*m1);
		lock2.lock();
	}

private:
	std::unique_lock<std::mutex> lock2;
};

class CDriverContext;

class CAcquisitionServer final : public IDriverCallback
{
public:
	explicit CAcquisitionServer(const Kernel::IKernelContext& ctx);
	~CAcquisitionServer() override;

	IDriverContext& getDriverContext() const;
	size_t getClientCount() const { return m_connections.size(); }
	double getImpedance(const size_t index) { return (index < m_impedances.size()) ? m_impedances[index] : OVAS_Impedance_Unknown; }

	bool loop();

	bool connect(IDriver& driver, IHeader& headerCopy, const uint32_t nSamplingPerSentBlock, const uint32_t port);
	bool start();
	bool stop();
	bool disconnect();

	// Driver samples information callback
	void setSamples(const float* samples) override;
	void setSamples(const float* samples, const size_t count) override;
	void setStimulationSet(const CStimulationSet& stimSet) override;

	// Driver context callback
	bool isConnected() const { return m_isInitialized; }
	bool isStarted() const { return m_isStarted; }
	bool updateImpedance(const size_t index, const double impedance);

	// General parameters configurable from the GUI
	ENaNReplacementPolicy getNaNReplacementPolicy() const { return m_eNaNReplacementPolicy; }
	uint64_t getOversamplingFactor() const { return m_overSamplingFactor; }
	bool isImpedanceCheckRequested() const { return m_Driver ? m_Driver->getHeader()->isImpedanceCheckRequested() : false; }
	bool isChannelSelectionRequested() const { return m_isChannelSelectionRequested; }
	void setNaNReplacementPolicy(const ENaNReplacementPolicy policy) { m_eNaNReplacementPolicy = policy; }
	bool setOversamplingFactor(const uint64_t oversamplingFactor);
	void setImpedanceCheckRequest(const bool active) { m_isImpedanceCheckRequested = active; }
	void setChannelSelectionRequest(const bool active) { m_isChannelSelectionRequested = active; }

	std::vector<IAcquisitionServerPlugin*> getPlugins() const { return m_Plugins; }

	bool acceptNewConnection(Socket::IConnection* connection);

	//---------- Variables ----------
	// See class DoubleLock
	std::mutex m_ProtectionMutex;
	std::mutex m_ExecutionMutex;

	std::deque<std::vector<float>> m_PendingBuffers;
	std::vector<float> m_SwapBuffers;

	size_t m_nSample   = 0;
	size_t m_startTime = 0;

	CDriftCorrection m_DriftCorrection;
	CStimulationSet m_PendingStimSet;

	std::vector<IAcquisitionServerPlugin*> m_Plugins;

protected:
	static bool requestClientThreadQuit(CConnectionClientHandlerThread* th);

	//---------- Variables ----------
	std::mutex m_oPendingConnectionProtectionMutex;
	std::mutex m_oPendingConnectionExecutionMutex;

	std::thread* m_connectionServerHandlerStdThread = nullptr;

	const Kernel::IKernelContext& m_kernelCtx;
	CDriverContext* m_driverCtx = nullptr;
	IDriver* m_Driver           = nullptr;
	const IHeader* m_headerCopy = nullptr;

	Kernel::IAlgorithmProxy* m_encoder = nullptr;
	Kernel::TParameterHandler<uint64_t> ip_subjectID;
	Kernel::TParameterHandler<uint64_t> ip_subjectAge;
	Kernel::TParameterHandler<uint64_t> ip_subjectGender;
	Kernel::TParameterHandler<CMatrix*> ip_matrix;
	Kernel::TParameterHandler<CMatrix*> ip_channelUnits;
	Kernel::TParameterHandler<uint64_t> ip_sampling;
	Kernel::TParameterHandler<CStimulationSet*> ip_stimSet;
	Kernel::TParameterHandler<uint64_t> ip_bufferDuration;
	Kernel::TParameterHandler<CMemoryBuffer*> op_buffer;

	Kernel::TParameterHandler<bool> ip_encodeChannelLocalisationData;
	Kernel::TParameterHandler<bool> ip_encodeChannelUnitData;

	std::list<std::pair<Socket::IConnection*, connection_info_t>> m_connections;
	std::list<std::pair<Socket::IConnection*, connection_info_t>> m_pendingConnections;
	std::vector<float> m_overSamplingSwapBuffers;
	std::vector<double> m_impedances;
	std::vector<size_t> m_selectedChannels;
	std::vector<CString> m_selectedChannelNames;
	Socket::IConnectionServer* m_connectionServer = nullptr;

	ENaNReplacementPolicy m_eNaNReplacementPolicy = ENaNReplacementPolicy::LastCorrectValue;
	bool m_replacementInProgress                  = false;

	bool m_isInitialized               = false;
	bool m_isStarted                   = false;
	bool m_isImpedanceCheckRequested   = false;
	bool m_isChannelSelectionRequested = false;
	bool m_gotData                     = false;
	size_t m_overSamplingFactor        = 0;
	size_t m_nChannel                  = 0;
	size_t m_sampling                  = 0;
	size_t m_nSamplePerSentBlock       = 0;
	size_t m_nLastSample               = 0;
	size_t m_lastDeliveryTime          = 0;

	//CDriftCorrection m_DriftCorrection;

	size_t m_nJitterEstimationForDrift   = 0;
	size_t m_driverTimeoutDuration       = 0;			// ms after which the driver is considered having time-outed
	int64_t m_startedDriverSleepDuration = 0;			// ms, <0 == spin, 0 == yield thread, >0 sleep. Used when driver does not return samples.
	size_t m_stoppedDriverSleepDuration  = 0;			// ms to sleep when driver is not running

	uint8_t* m_sampleBuffer = nullptr;
	//CStimulationSet m_PendingStimSet;
};
}  // namespace AcquisitionServer
}  // namespace OpenViBE
