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

//////////////////////////////////////////////////////////////////////////
/// \file
/// \brief Utilities to aid in sending %SCPI commands and parsing of %SCPI reponses.

#pragma once

// Standard includes
#include <cstdlib>
#include <string>
#include <vector>
#include <regex>
#include <stdexcept>

// Personal includes

namespace MTL {
	namespace SCPI {

// Utilities
#define SCPIParsing_UNUSED_VAR(x)	(void)x;	// Prevents from unereferenced variable when compiling in release

		//----------------------------------------------------------------------//
		//							Type Definitions							//
		//----------------------------------------------------------------------//
		typedef std::vector<unsigned char>			tChannel;		///< %SCPI channel
		typedef std::vector<tChannel>				tChannelList;	///< %SCPI channel list

		//----------------------------------------------------------------------//
		//							Conversion Utilities						//
		//----------------------------------------------------------------------//
        /// \brief Find arbitrary-block data within a buffer.
		/// \tparam iterator_type	Type of iterator
		/// \param[in]	first			Beginning of buffer.
		/// \param[in]	last			End of buffer.
		/// \param[out]	rStartOffset	Starting offset of the arbitrary-block data.
		/// \param[out]	rLength			Length of the arbitrary-block data.
		/// \return True if successful.
		template <class iterator_type>
		bool IsArbitraryBlock(const iterator_type first, const iterator_type last, size_t & rStartOffset, size_t & rLength);
		
		/// \brief Package data as an arbitrary block.
		/// \param[in]	rStr			Data to be packaged.
		/// \param[out]	rArbitraryBlock	Resulting arbitray block.
		/// \param[in]	InfiniteFormat	Whether or not to use "infinite format" (header = "#0")
		void ToArbitraryBlock(const std::string & rStr, std::string & rArbitraryBlock, bool InfiniteFormat = false);
		
		/// \brief Decode channel list string.
		/// \tparam iterator_type	Type of iterator
		/// \param[in]	first			Beginning of buffer.
		/// \param[in]	last			End of buffer.
		/// \param[out]	rChannelList	Resulting channel list.
		template <class iterator_type>
		void FromStringChannelList(iterator_type first, iterator_type last, tChannelList & rChannelList);
		
		/// \brief Encode channel list string.
		/// \param[in]	rChannelList	Channel list.
		/// \param[out]	rStr			Resulting string.
		void ToStringChannelList(const tChannelList & rChannelList, std::string & rStr);
		
		/// \brief Decode binary channel list.
		/// \param[in]	pBinaryChanList	Pointer to binary channel list.
		/// \param[in]	Len				Length of binary channel list, in bytes.
		/// \param[out]	rChannelList	Resulting channel list.
		void FromBinaryChannelList(const char * pBinaryChanList, size_t Len, tChannelList & rChannelList);
		
		/// \brief Encode binary channel list.
		/// \param[in]	rChannelList	Channel list.
		/// \param[out]	rBinaryChanList	Resulting binary channel list.
		void ToBinaryChannelList(const tChannelList & rChannelList, std::vector<char> & rBinaryChanList);
		
		/// \brief Split a string into substrings separated by a given character.
		/// \param[in]	rStr			String to be split.
		/// \param[out]	rStrings		Resulting substrings.
		/// \param[in]	Sep				Separator character.
		void SplitString(const std::string & rStr, std::vector<std::string> & rStrings, char Sep);


		//----------------------------------------------------------------------//
		//						Conversion Utilities Definitions				//
		//----------------------------------------------------------------------//
		// Template definitions must be located in the header file to avoid
		// linking errors.
		template <class iterator_type>
		bool IsArbitraryBlock(const iterator_type first, const iterator_type last, size_t & rStartOffset, size_t & rLength)
		{
			try
			{
				// Check arbitrary format
				if (*first != '#')
					throw false;

				// "Indefinite format": #0...
				if (*(first+1) == '0')
				{
					rStartOffset = 2;
					rLength = last - first - rStartOffset;
				}

				// "Definite length format": #nmmm...
				else
				{
					// Get mmm
					size_t l_PrefixLen = std::stoul(std::string(&*(first+1), 1));
					rStartOffset = 2 + l_PrefixLen;
					rLength = std::stoul(std::string(&*(first+2), l_PrefixLen));

					// Check mmm
					size_t l_BufferLength = last - first - rStartOffset;
					if (rLength > l_BufferLength)
						throw false;		// String contains less data that indicated by the prefix
				}
			}
			catch (bool & rE)
			{
				SCPIParsing_UNUSED_VAR(rE)
				rStartOffset = 0;
				rLength = 0;
				return false;
			}
			// Possible exception thrown by stoul()
			catch (std::invalid_argument & rE)
			{
				SCPIParsing_UNUSED_VAR(rE)
				rStartOffset = 0;
				rLength = 0;
				return false;
			}
			// Possible exception thrown by stoul()
			catch (std::out_of_range & rE)
			{
				SCPIParsing_UNUSED_VAR(rE)
				rStartOffset = 0;
				rLength = 0;
				return false;
			}

			return true;
		}
		inline void ToArbitraryBlock(const std::string & rStr, std::string & rArbitraryBlock, bool InfiniteFormat)
		{
			if (InfiniteFormat)
				// Infinite format: #0...
				rArbitraryBlock = "#0" + rStr;
			else
			{
				// Definite length format: #nmmm...
				std::string l_mmm = std::to_string(rStr.length());
				rArbitraryBlock = "#" + std::to_string(l_mmm.length()) + l_mmm + rStr;
			}
		}
		template <class iterator_type>
		void FromStringChannelList(iterator_type first, iterator_type last, tChannelList & rChannelList)
		{
			rChannelList.clear();
			// Check channel list format
			if (std::regex_match(first, last, std::regex("^\\(@[0-9!,:]*\\)[^]*$")))
			{
				// Check empty channel
				if (std::regex_match(first, last, std::regex("^\\(@\\)[^]*$")))
					return;

				tChannel l_Chan;
				for (size_t l_Index = 2, PortStart = 2; l_Index < (size_t)(last - first); l_Index++)
				{
					char c = *(first + l_Index);
					if (c >= '0' && c <= '9')	// In port definition
					{
					}
					else if (c == '!')			// End of port
					{
						l_Chan.push_back(atoi(std::string(first + PortStart, first + l_Index).c_str()));
						PortStart = l_Index + 1;
					}
					else if (c == ',')			// End of channel
					{
						// Close port
						l_Chan.push_back(atoi(std::string(first + PortStart, first + l_Index).c_str()));
						// Add channel
						rChannelList.push_back(l_Chan);
						l_Chan.clear();
						PortStart = l_Index + 1;
					}
					else if (c == ')')			// End of channel list
					{
						// Close port
						l_Chan.push_back(atoi(std::string(first + PortStart, first + l_Index).c_str()));
						// Add channel
						rChannelList.push_back(l_Chan);
						// Exit
						l_Index = last - first;
					}
					else
					{
					}
				}
			}
		}
		inline void ToStringChannelList(const tChannelList & rChannelList, std::string & rStr)
		{
			// Open channel list string
			rStr = "(@";
			// Loop over channels
			for (tChannelList::const_iterator l_it = rChannelList.begin(); l_it != rChannelList.end(); l_it++)
			{
				// Loop over ports
				for (tChannel::const_iterator portit = l_it->begin(); portit != l_it->end(); portit++)
				{
					rStr += std::to_string(*portit);
					if (portit + 1 != l_it->end())
						rStr += '!';
				}
				if (l_it + 1 != rChannelList.end())
					rStr += ',';
			}
			// Close channel list string
			rStr += ")";
		}
		inline void FromBinaryChannelList(const char * pBinaryChanList, size_t Len, tChannelList & rChannelList)
		{
			rChannelList.clear();
			tChannel l_Chan;
			for (size_t l_Index = 0; l_Index < Len; l_Index++)
			{
				if (pBinaryChanList[l_Index] != 0)
					l_Chan.push_back(pBinaryChanList[l_Index]);
				else
				{
					rChannelList.push_back(l_Chan);
					l_Chan.clear();
				}
			}
			if (!l_Chan.empty())
				rChannelList.push_back(l_Chan);
		}
		inline void ToBinaryChannelList(const tChannelList & rChannelList, std::vector<char> & rBinaryChanList)
		{
			rBinaryChanList.clear();
			// Loop over channels
			for (tChannelList::const_iterator l_it = rChannelList.begin(); l_it != rChannelList.end(); l_it++)
			{
				// Loop over ports
				for (tChannel::const_iterator portit = l_it->begin(); portit != l_it->end(); portit++)
				{
					rBinaryChanList.push_back(*portit);
				}
				if (l_it + 1 != rChannelList.end())
					rBinaryChanList.push_back(0);
			}
		}
		inline void SplitString(const std::string & rStr, std::vector<std::string> & rStrings, char Sep)
		{
			rStrings.clear();

			// Check if there is data to split
			if (rStr.empty())
				return;

			// Find first separator (if any)
			size_t l_Start = 0;
			size_t l_Next = rStr.find_first_of(Sep);
			rStrings.push_back(rStr.substr(l_Start, l_Next - l_Start));

			// Find all separators
			for (l_Start = l_Next + 1;
				l_Next != std::string::npos;
				l_Start = l_Next + 1)
			{
				l_Next = rStr.find_first_of(Sep, l_Start);
				rStrings.push_back(rStr.substr(l_Start, l_Next - l_Start));
			}
		}

#undef SCPIParsing_UNUSED_VAR

}}	// namespace MTL::SCPI
