/**
 * @file Decoders.h
 * Provides a set of TTCN-3 recoder decoder  methods
 * @author Yann garcia
 * @version 1.0
 * @date 20/07/2009
 */

#ifndef STF_370_Decoders
#define STF_370_Decoders

#include <string>
#include <iostream>
#include <iomanip>

#include "Singleton.h"

/*! Class Decoders
 * \brief This class provides a set of convesion methods. It shall be used with Singleton class.
 */
class Decoders : public Singleton<Decoders>
{
private:
  //! Singleton implementation
  friend class Singleton<Decoders>;
  
  //! Convertion map from char to int.
  std::map<char, int> m_char2digits;
  
  //! Decoders.
 /*! Default ctor.
  */
  Decoders()
  {
    m_char2digits['0'] = 0x00;
    m_char2digits['1'] = 0x01;
    m_char2digits['2'] = 0x02;
    m_char2digits['3'] = 0x03;
    m_char2digits['4'] = 0x04;
    m_char2digits['5'] = 0x05;
    m_char2digits['6'] = 0x06;
    m_char2digits['7'] = 0x07;
    m_char2digits['8'] = 0x08;
    m_char2digits['9'] = 0x09;
    m_char2digits['a'] = 0x0a;
    m_char2digits['b'] = 0x0b;
    m_char2digits['c'] = 0x0c;
    m_char2digits['d'] = 0x0d;
    m_char2digits['e'] = 0x0e;
    m_char2digits['f'] = 0x0f;
  };

public:
  //! DecodeByte.
  /*! Decode a byte, one byte without length.
   * @param frame: The hexadecimal string
   * @param [out/out] poffset: The current decoder index
   * @return The message type.
   */
  unsigned char DecodeByte(std::string frame, int *poffset)
  {
    // Sanity checks.
    if (frame.length() < (*poffset + 2))
    {
      return 0xff;
    }
    
    // Extract the length.
    try
    {
      return static_cast<unsigned char>(m_char2digits[tolower(frame[(*poffset)++])] << 4 | m_char2digits[tolower(frame[(*poffset)++])]);
    }
    catch (const std::exception& e)
    {
      return 0xff;
    }
  };
  
  //! DecodeLengthPlusString.
  /*! Decode a bytes sequence <length> + <string>, with <length> on 2 bytes.
   * @param frame: The hexadecimal string
   * @param [out/out] poffset: The current decoder index
   * @param [out] pLength: The length of the decoded string.
   * @return The real string on success, an empty string otherwise. On error, *pLength is set to -1.
   */
  std::string DecodeLengthPlusString(std::string frame, int *pOffset, int *pLength)
  {
    *pLength = -1;
    
    // Sanity checks.
    if (frame.length() < (*pOffset + 2 * sizeof(short)))
    {
      std::cerr << "DecodeLengthPlusString: bad offset value: " << *pOffset << std::endl;
      return std::string("");
    }
    
    try
    {
      // Extract the length.
      int ended = *pOffset + 2 * sizeof(short);
      //std::clog << "DecodeLengthPlusString: ended: " << ended << std::endl;
      *pLength = 0;
      for (std::string::const_iterator it = frame.begin() + *pOffset; it != frame.begin() + ended; ++it)
      {
        if ((*it == '0') && (*(it + 1) == '0'))
        {
          it += 1;
        }
        else
        {
          *pLength <<= 8;
          *pLength |= static_cast<unsigned char>(m_char2digits[tolower(*it++)] << 4 | m_char2digits[tolower(*it)]);
        }
        *pOffset += 2;
      }
      
      //std::cout << "DecodeLengthPlusString: length value:" << *pLength << " - " << *pOffset << std::endl;
      if (*pLength == 0)
      {
        std::cerr << "DecodeLengthPlusString: length is null" << std::endl;
        return std::string("");
      }
      
      std::ostringstream hexStream;
      //hexStream << std:setw(2);
      ended = *pOffset + 2 * *pLength;
      for (std::string::const_iterator it = frame.begin() + *pOffset; it != frame.begin() + ended; ++it)
      {
        hexStream << static_cast<char>(m_char2digits[tolower(*it++)] << 4 | m_char2digits[tolower(*it)]);
        *pOffset += 2;
      }
        
      return hexStream.str();
    }
    catch (const std::exception& e)
    {
      *pLength = -1;
      return std::string("");
    }
  };
  
  //! DecodeUInt16.
  /*! Decode a bytes sequence <Integer> on 2 bytes.
   * @param frame: The hexadecimal string
   * @param [out/out] pOffset: The current decoder index
   * @return The number of parameters value on success, -1 otherwise.
   */
  int DecodeUInt16(std::string frame, int *pOffset)
  {
    // Sanity checks.
    if (frame.length() < (*pOffset + 2 * sizeof(short)))
    {
      std::cerr << "DecodeUInt16: bad offset value: " << *pOffset << std::endl;
      return -1;
    }
    
    try
    {
      // Extract the length.
      int ended = *pOffset + 2 * sizeof(short);
      //std::clog << "DecodeUInt16: ended: " << ended << std::endl;
      int conv = 0;
      for (std::string::const_iterator it = frame.begin() + *pOffset; it != frame.begin() + ended; ++it)
      {
        if ((*it == '0') && (*(it + 1) == '0'))
        {
          it += 1;
        }
        else
        {
          conv <<= 8;
          conv |= static_cast<unsigned char>(m_char2digits[tolower(*it++)] << 4 | m_char2digits[tolower(*it)]);
        }
        *pOffset += 2;
      }
      
        
      return conv;
    }
    catch (const std::exception& e)
    {
      return -1;
    }
  };
  
  //! DecodeParamsNum.
  /*! Decode a bytes sequence <Integer> on 2 bytes.
   * @param frame: The hexadecimal string
   * @param [out/out] pOffset: The current decoder index
   * @return The number of parameters value on success, -1 otherwise.
   */
  int DecodeParamsNum(std::string frame, int *pOffset)
  {
    return DecodeUInt16(frame, pOffset);
  };
  
  //! DecodeInteger.
  /*! Decode a bytes sequence <Integer> on 8 bytes.
   * @param frame: The hexadecimal string
   * @param [out/out] pOffset: The current decoder index
   * @return The integer value on success, -1 otherwise.
   */
  int DecodeInteger(std::string frame, int *pOffset)
  {
    // Sanity checks.
    if (frame.length() < (*pOffset + 2 * sizeof(long long)))
    {
      return -1;
    }
    
    // Extract the length.
    try
    {
      int ended = *pOffset + 2 * sizeof(long long);
      int conv = 0;
      for (std::string::const_iterator it = frame.begin() + *pOffset; it != frame.begin() + ended; ++it)
      {
        conv <<= 8;
        conv |= static_cast<unsigned char>(m_char2digits[tolower(*it++)] << 4 | m_char2digits[tolower(*it)]);

        *pOffset += 2;
      }
      
      return conv;
    }
    catch (const std::exception& e)
    {
      return -1;
    }
  };
  
  //! DecodeUInt32.
  /*! Decode a bytes sequence <Integer> on 4 bytes.
   * @param frame: The hexadecimal string
   * @param [out/out] pOffset: The current decoder index
   * @return The integer value on success, -1 otherwise.
   */
  int DecodeUInt32(std::string frame, int *pOffset)
  {
    // Sanity checks.
    if (frame.length() < (*pOffset + 2 * sizeof(int)))
    {
      return -1;
    }
    
    // Extract the length.
    try
    {
      int ended = *pOffset + 2 * sizeof(int);
      int conv = 0;
      for (std::string::const_iterator it = frame.begin() + *pOffset; it != frame.begin() + ended; ++it)
      {
        conv <<= 8;
        conv |= static_cast<unsigned char>(m_char2digits[tolower(*it++)] << 4 | m_char2digits[tolower(*it)]);

        *pOffset += 2;
      }
      
      return conv;
    }
    catch (const std::exception& e)
    {
      return -1;
    }
  };
  
  //! DecodeEnumerated.
  /*! Decode a bytes sequence <length> + <Integer>.
   * @param frame: The hexadecimal string
   * @param [out/out] pOffset: The current decoder index
   * @return The byte value on success, 0xff otherwise.
   */
  unsigned char DecodeEnumerated(std::string frame, int *pOffset)
  {
    // Sanity checks.
    if (frame.length() < (*pOffset + 2))
    {
      return 0xff;
    }
    
    // Extract the length.
    try
    {
      return static_cast<unsigned char>(m_char2digits[tolower(frame[(*pOffset)++])] << 4 | m_char2digits[tolower(frame[(*pOffset)++])]);
    }
    catch (const std::exception& e)
    {
      return 0xff;
    }
  };
};

#endif //  STF_370_Decoders
