// Copyright (c) 2020 Metrolab Technology S.A., Geneva, Switzerland (www.metrolab.com)
// See the included file LICENSE.txt for the licensing conditions.

//////////////////////////////////////////////////////////////////////////
/// \file
/// \brief IEEE488.2 / SCPI instrument I/O: Instrument buffer management.

#pragma once

#include <iterator>
#include <algorithm>

#include "Synchronization.h"

using namespace MTL::Synchronization;

namespace MTL {
	namespace Instrument {

	#define MTL_INSTRUMENT_BUFFER_TYPE		char

	//----------------------------------------------------------------------//
	/// \brief Instrument Buffer
	///
	/// This class is intended to provide an efficient way of reading data
	/// and parsing a SCPI response.
	/// Read operations require an allocated buffer pointer and its length.
	/// Parsing methods (regex, find etc...) require std::string object or
	/// iterators.
	///  - std::string doesn't ensure that its data is stored contiguously:
	/// 	cannot write to it through a pointer.
	///  - std::vector ensures contiguous storage and it is allowed to write to
	/// 	its allocated storage (capacity) through a pointer. But it is
	/// 	impossible to set its size. It is thus not possible to indicate how
	/// 	many bytes have been stored into the vector after a read.
	///
	/// This class holds data in a std::vector. Storage is thus allocated
	/// in contiguous memory and memory allocation/deallocation is automatically handled.
	/// A size member is managed by this class itself and can be set by a
	/// method. We must ensure that this class size is always less than or equal
	/// to the contained vector size (the class capacity is equivalent to the
	/// contained vector size).
	class CSCPIBuffer
	{
	private:
		mutable CRecursiveMutex				m_Lock;
		std::vector<MTL_INSTRUMENT_BUFFER_TYPE>	m_Vector;	// Used to store the data contiguously
		size_t								m_Size;		// Used to handle the real size
	public:
		/// \brief Constructor: new empty buffer.
		/// \param[in]	InitialCapacity		Initial buffer size, in bytes.
		CSCPIBuffer(size_t InitialCapacity = 2048)
			: m_Vector(), m_Size(0)
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			reserve(InitialCapacity);
		}
		
		/// \brief Constructor: initialize with data provided.
		/// \param[in]	pData				Data buffer location.
		/// \param[in]	DataLen				Data buffer size, in bytes.
		CSCPIBuffer(const MTL_INSTRUMENT_BUFFER_TYPE * pData, size_t DataLen)
			: m_Vector(), m_Size(0)
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			assign(pData, pData + DataLen);
		}
		virtual ~CSCPIBuffer()
		{}									///< Destructor.

        /// \brief Clear by setting the buffer size to zero.
		void clear()
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			m_Size = 0;
		}
		
		/// \brief Allocate at least a given amount of space.
		/// \param[in]	capacity			Desired capacity.
		void reserve(size_t capacity)
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			if (capacity > m_Vector.size())
				m_Vector.resize(capacity);
		}
		
		/// \brief Resize the buffer.
		/// \param[in]	size				New buffer size.
		void resize(size_t size)
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			if (size > m_Vector.size())
			{
				// Ensure the vector can contain at least the required size
				m_Vector.resize(size);
			}
			m_Size = size;
		}

		/// \brief Return the buffer size.
		size_t size() const
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			return m_Size;
		}
		
		/// \brief Return the buffer capacity (allocated size).
		size_t capacity() const
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			return m_Vector.size();
		}

		/// \brief Return an iterator to the beginning of the buffer.
		std::vector<MTL_INSTRUMENT_BUFFER_TYPE>::iterator begin()
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			return m_Vector.begin();
		}
		
		/// \brief Return a const iterator to the beginning of the buffer.
		std::vector<MTL_INSTRUMENT_BUFFER_TYPE>::const_iterator begin() const
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			return m_Vector.begin();
		}

		/// \brief Return an iterator to the end of the buffer.
		std::vector<MTL_INSTRUMENT_BUFFER_TYPE>::iterator end()
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			return m_Vector.begin() + m_Size;
		}

		/// \brief Return a const iterator to the end of the buffer.
		std::vector<MTL_INSTRUMENT_BUFFER_TYPE>::const_iterator end() const
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			return m_Vector.begin() + m_Size;
		}

        /// \brief Return a pointer to the data.
		MTL_INSTRUMENT_BUFFER_TYPE* data() noexcept
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			return m_Vector.data();
		}

		/// \brief Return a const pointer to the data.
		const MTL_INSTRUMENT_BUFFER_TYPE* data() const noexcept
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			return m_Vector.data();
		}

        /// \brief Return byte n of the buffer.
		MTL_INSTRUMENT_BUFFER_TYPE& operator[] (size_t n)
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			return m_Vector[n];
		}

		/// \brief Return byte n of the buffer as const.
		const MTL_INSTRUMENT_BUFFER_TYPE& operator[] (size_t n) const
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			return m_Vector[n];
		}

		/// \brief Copy the given data into the buffer: variant with begin/end.
		/// \param[in]	First				Pointer to first byte of data.
		/// \param[in]	Last				Pointer to first byte past the data.
		void assign(const MTL_INSTRUMENT_BUFFER_TYPE* First, const MTL_INSTRUMENT_BUFFER_TYPE* Last)
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			if (capacity() < (size_t)(Last - First))
				reserve(Last - First);
			std::copy(First, Last, m_Vector.begin());
			m_Size = Last - First;
		}

		/// \brief Copy the given data into the buffer: variant with pointer/size.
		/// \param[in]	pData				Pointer to first byte of data.
		/// \param[in]	n					Number of bytes.
		void assign(const MTL_INSTRUMENT_BUFFER_TYPE* pData, size_t n)
		{
			CLockGuard<CRecursiveMutex> Lock(m_Lock);

			if (capacity() < n)
				reserve(n);
			std::copy(pData, pData + n, m_Vector.begin());
			m_Size = n;
		}
	};

	//----------------------------------------------------------------------//
	/// \brief SCPI buffer parser
	///
	/// Class to help parse %SCPI reponse strings.
	class CSCPIBufferParser
	{
	private:
		std::vector<char>::const_iterator	m_Bbegin;
		std::vector<char>::const_iterator	m_Bend;
		std::vector<char>::const_iterator	m_SplitOffset;
	public:
        /// \brief Location of a token within a buffer.
		struct sToken {
			std::vector<char>::const_iterator	begin;		///< Beginning of the token.
			std::vector<char>::const_iterator	end;		///< End of the token.
			/// \brief Constructor.
			/// \param[in]	b	Beginning of the token.
			/// \param[in]	e	End of the token.
			sToken(std::vector<char>::const_iterator b, std::vector<char>::const_iterator e)
				: begin(b), end(e) {}
		};
		typedef std::vector<sToken>				tTokens;	///< List of tokens.

	public:
		/// \brief Constructor.
		/// \param[in]	begin	Beginning of the data to be parsed.
		/// \param[out]	end		End of the data to be parsed.
		CSCPIBufferParser(std::vector<char>::const_iterator begin, std::vector<char>::const_iterator end)
			: m_Bbegin(begin), m_Bend(end), m_SplitOffset(begin) {}

		/// \brief Return the beginning of the data to be parsed.
		std::vector<char>::const_iterator begin()
		{
			return m_Bbegin;
		}

		/// \brief Return the end of the data to be parsed.
		std::vector<char>::const_iterator end()
		{
			return m_Bend;
		}

        /// \brief Split the buffer into tokens.
		/// \param[in]	Separator	Separator between tokens.
		/// \param[in]	Offset		Starting offset, relative to the beginning of the data to be parsed.
		/// \return List of tokens found.
		const tTokens Tokenize(const char Separator = ';', size_t Offset = 0)
		{
			tTokens l_Tokens;

			// Check if there is data to split
			if (m_Bbegin == m_Bend)
				return l_Tokens;

			// Find first separator (if any).
			std::vector<char>::const_iterator l_Start = m_Bbegin + Offset;
			std::vector<char>::const_iterator l_Next = std::find(l_Start, m_Bend, Separator);
			l_Tokens.push_back(sToken(l_Start, l_Next));

			// Find all separators
			while (l_Next != m_Bend)
			{
				l_Start = l_Next + 1;
				l_Next = std::find(l_Start, m_Bend, Separator);
				l_Tokens.push_back(sToken(l_Start, l_Next));
			}
			
			// Strip white space off front.
			const std::string l_Whitespace(" \t\n\r");
			for (auto l_Token : l_Tokens)
			{
				while (std::string::npos != l_Whitespace.find(*l_Token.begin))
					l_Token.begin++;
			}
			
			return l_Tokens;
		}

		/// \brief Find the next token.
		///
		/// **Attention:** When next token is an arbitrary block, it may contain a ';', which causes rNextEnd to be wrong.
		/// \param[out]	rNextBegin		Beginning of next token.
		/// \param[out]	rNextEnd		End of next token.
		/// \param[in]	Separator		Separator between tokens.
		/// \return True if successful.
		bool GetNext(std::vector<char>::const_iterator & rNextBegin, std::vector<char>::const_iterator & rNextEnd, const char Separator = ';')
		{
			// Strip white space off front.
			const std::string l_Whitespace(" \t\n\r");
			while (m_SplitOffset < m_Bend &&
				   std::string::npos != l_Whitespace.find(*m_SplitOffset))
				m_SplitOffset++;
			
			// Did we reach the last token?
			if (m_SplitOffset == m_Bend)
			{
				rNextBegin = m_Bend;
				rNextEnd = m_Bend;
				return false;
			}
			
			// Find next
			rNextBegin = m_SplitOffset;
			rNextEnd = std::find(m_SplitOffset, m_Bend, Separator);
			
			// Did we reach the last token?
			if (rNextEnd == m_Bend)
				m_SplitOffset = m_Bend;			// Set offset to the ending interator
			else
				m_SplitOffset = rNextEnd + 1;	// Skip separator for next call

			return true;
		}

		/// \brief Manually set the offset to the next token.
		///
		/// When parsing arbitrary block, it is necessary to force offset to skip ';' chars that are not separators.
		/// \param[in]	Offset			Offset to next token.
		void SetNextOffset(std::vector<char>::const_iterator Offset)
		{
			m_SplitOffset = Offset;
			
			// Check bounds
			if (m_SplitOffset < m_Bbegin)
				m_SplitOffset = m_Bbegin;
			if (m_SplitOffset > m_Bend)
				m_SplitOffset = m_Bbegin;
		}
	};

}}	// namespace MTL::Instrument
