/*
;    Project:       Open Vehicle Monitor System
;    Date:          14th March 2017
;
;    Changes:
;    1.0  Initial release
;
;    (C) 2011       Michael Stegen / Stegen Electronics
;    (C) 2011-2017  Mark Webb-Johnson
;    (C) 2011        Sonny Chen @ EPRO/DX
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.
*/

#ifndef __OVMS_UTILS_H__
#define __OVMS_UTILS_H__

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <cstring>
#include <string>
#include <iomanip>
#include <vector>
#include "ovms.h"

// Macro utils:
// see https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html#Stringizing
// STR(x) = string of x, x expanded if preprocessor macro
#ifndef STR
#define STRX(x)   #x
#define STR(x)    STRX(x)
#endif

// Math utils:
#define SQR(n) ((n)*(n))
#define ABS(n) (((n) < 0) ? -(n) : (n))
#define LIMIT_MIN(n,lim) ((n) < (lim) ? (lim) : (n))
#define LIMIT_MAX(n,lim) ((n) > (lim) ? (lim) : (n))

// Value precision utils:
#define TRUNCPREC(fval,prec) (trunc((fval) * pow(10,(prec))) / pow(10,(prec)))
#define ROUNDPREC(fval,prec) (round((fval) * pow(10,(prec))) / pow(10,(prec)))
#define CEILPREC(fval,prec)  (ceil((fval)  * pow(10,(prec))) / pow(10,(prec)))

// Standard array size (number of elements):
#if __cplusplus < 201703L
  template <class T, std::size_t N>
  constexpr std::size_t sizeof_array(const T (&array)[N]) noexcept
    {
    return N;
    }
#else
  #define sizeof_array(array) (std::size(array))
#endif

// container.insert() helper:
#define endof_array(array) ((array)+sizeof_array(array))

// C string sorting for std::map et al:
struct CmpStrOp
  {
  bool operator()(char const *a, char const *b)
    {
    return std::strcmp(a, b) < 0;
    }
  };

// Tolerant boolean string analyzer:
inline bool strtobool(const std::string& str)
  {
  return (str == "yes" || str == "1" || str == "true");
  }

/**
 * chargestate_code: convert legacy chargestate key to code
 * chargestate_key: convert chargestate code to legacy key
 */
std::string chargestate_code(const int key);
int chargestate_key(const std::string code);

/**
 * chargesubstate_code: convert legacy charge substate key to code
 * chargesubstate_key: convert charge substate code to legacy key
 */
std::string chargesubstate_code(const int key);
int chargesubstate_key(const std::string code);

/**
 * chargemode_code: convert legacy chargemode key to code
 * chargemode_key: convert chargemode code to legacy key
 */
std::string chargemode_code(const int key);
int chargemode_key(const std::string code);


/**
 * mp_encode: encode string for MP transport;
 *  - replace '\r\n' by '\r'
 *  - replace '\n' by '\r'
 *  - replace ',' by ';'
 */
std::string mp_encode(const std::string text);
extram::string mp_encode(const extram::string text);

/**
 * stripcr:
 *  - replace '\r\n' by '\n'
 */
extram::string stripcr(const extram::string& text);

/**
 * stripesc: remove terminal escape sequences from (log) string
 */
std::string stripesc(const char* s);

/**
 * startsWith: std::string et al prefix check
 */
template <class string_t>
bool startsWith(const string_t& haystack, const std::string& needle)
  {
  return needle.length() <= haystack.length()
    && std::equal(needle.begin(), needle.end(), haystack.begin());
  }
template <class string_t>
bool startsWith(const string_t& haystack, const char needle)
  {
  return !haystack.empty() && haystack.front() == needle;
  }

/**
 * endsWith: std::string et al suffix check
 */
template <class string_t>
bool endsWith(const string_t& haystack, const std::string& needle)
  {
  return needle.length() <= haystack.length()
    && std::equal(needle.begin(), needle.end(), haystack.end() - needle.length());
  }
template <class string_t>
bool endsWith(const string_t& haystack, const char needle)
  {
  return !haystack.empty() && haystack.back() == needle;
  }

/**
 * HexByte: Write a single byte as two hexadecimal characters
 * Returns new pointer to end of string (p + 2)
 */
char* HexByte(char* p, uint8_t byte);

/**
 * FormatHexDump: create/fill hexdump buffer including printable representation
 * Note: allocates buffer as necessary in *bufferp, caller must free.
 * Returns new remaining length
 */
size_t FormatHexDump(char** bufferp, const char* data, size_t rlength, size_t colsize=16);

/**
 * hexencode: encode a string of bytes into hexadecimal form
 */
std::string hexencode(const std::string value);

/**
 * hexdecode: decode a hexadecimal encoded string of bytes
 *  Returns empty string on error
 */
std::string hexdecode(const std::string encval);

/**
 * int_to_hex: hex encode an integer value
 *  Source: https://kodlogs.com/68574/int-to-hex-string-c
 */
template <typename T>
std::string int_to_hex(T i)
  {
  std::stringstream stream;
  stream << std::setfill('0') << std::setw(sizeof(T)*2) << std::hex << (unsigned)i;
  return stream.str();
  }

/**
 * json_encode: encode string for JSON transport (see http://www.json.org/)
 */
template <class src_string>
std::string json_encode(const src_string text)
  {
  std::string buf;
  char hex[10];
  buf.reserve(text.size() + (text.size() >> 3));
  for (int i=0; i<text.size(); i++)
    {
    switch(text[i])
      {
      case '\n':        buf += "\\n"; break;
      case '\r':        buf += "\\r"; break;
      case '\t':        buf += "\\t"; break;
      case '\b':        buf += "\\b"; break;
      case '\f':        buf += "\\f"; break;
      case '\"':        buf += "\\\""; break;
      case '\\':        buf += "\\\\"; break;
      default:
        if (iscntrl(text[i]))
          {
          sprintf(hex, "\\u%04x", (unsigned int)text[i]);
          buf += hex;
          }
        else
          {
          buf += text[i];
          }
        break;
      }
    }
  return buf;
  }

/**
 * display_encode: encode string displaying unprintablel characters
 * Emulates (linux) "cat -t" semantics
 */
template <class src_string>
std::string display_encode(const src_string text)
  {
  std::string buf;
  buf.reserve(text.size() + (text.size() >> 3));
  for (int i = 0; i < text.size(); i++)
    {
    char ch = text[i];
    if (!isascii(ch))
      {
      buf += "M-";
      ch = toascii(ch);
      }
    if (ch == '\177')
      {
      buf += "^?";
      continue;
      }
    if (ch == '\t')
      {
      buf += "^I";
      continue;
      }
    if (ch == '\n')
      {
      // deviation from "cat -t"
      buf += "^J";
      continue;
      }
    if (!isprint(ch))
      {
      ch = ch ^ 0x40;
      buf += "^";
      }
    buf += ch;
    }
  return buf;
  }


/**
 * mqtt_topic: convert dotted string (e.g. notification subtype) to MQTT topic
 *  - replace '.' by '/'
 */
std::string mqtt_topic(const std::string text);


/**
 * pwgen: simple password generator
 * Note: take care to seed the pseudo random generator i.e. by
 *  srand48(StdMetrics.ms_m_monotonic->AsInt() * StdMetrics.ms_m_freeram->AsInt());
 */
std::string pwgen(int length);

#ifdef CONFIG_FREERTOS_USE_TRACE_FACILITY
#define HAVE_TaskGetHandle
/**
 * TaskGetHandle: get task handle by name
 * (FreeRTOS xTaskGetHandle() is not available)
 */
TaskHandle_t TaskGetHandle(const char *name);
#endif // CONFIG_FREERTOS_USE_TRACE_FACILITY


/**
 * mkpath: mkdir -p
 */
int mkpath(std::string path, mode_t mode = 0);

/**
 * rmtree: rmdir -r
 */
int rmtree(const std::string path);

/**
 * path_exists: check if filesystem path exists
 */
bool path_exists(const std::string path);

/**
 * load & save file to/from string
 *  - saving creates missing directories automatically & signals system.vfs.file.changed
 *  - return value: 0 = ok / errno
 */
int load_file(const std::string &path, extram::string &content);
int save_file(const std::string &path, extram::string &content);

/**
 * get_user_agent: create User-Agent string from OVMS versions & vehicle ID
 *  Scheme: "ovms/v<hw_version> (<vehicle_id> <sw_version>)"
 */
std::string get_user_agent();

/**
 * float2double: minimize precision errors on float→double conversion
 */
double float2double(float f);

/**
 * idtag: create object instance tag for registrations
 */
std::string idtag(const char* tag, void* instance);
#define IDTAG idtag(TAG,this)

/**
 * sign_extend: Sign extend an unsigned to a signed integer of the same or bigger size.
 *  Sign bit is not known at compile time.
 */
template<typename UINT, typename INT>
INT sign_extend( UINT uvalue, uint8_t signbit)
  {
  typedef typename std::make_unsigned<INT>::type uint_t;
  uint_t newuvalue = uvalue;
  UINT signmask = UINT(1U) << signbit;
  if ( newuvalue & signmask)
    newuvalue |= ~ (static_cast<uint_t>(signmask) - 1);
  return reinterpret_cast<INT &>(uvalue);
  }

/**
 * sign_extend: Sign extend an unsigned to a signed integer of the same or bigger size.
 * Sign bit is known at compile time.
 */
template<typename UINT, typename INT, uint8_t SIGNBIT>
INT sign_extend( UINT uvalue)
  {
  typedef typename std::make_unsigned<INT>::type uint_t;
  uint_t newuvalue = uvalue;
  if ( newuvalue & ( UINT(1U) << SIGNBIT) )
    newuvalue |= ~((uint_t(1U) << SIGNBIT) - 1);
  return reinterpret_cast<INT &>(uvalue);
  }

/**
 * get_bit: Get at a specific (compile-time defined) bit in a byte.
 */
template<uint8_t BIT>
bool get_bit(uint8_t data)
  {
  return (0 != (data & (1 << BIT)));
  }
/**
 * get_bit: Get at a specific (run-time defined) bit in a byte.
 */
inline bool get_bit(uint8_t data, uint8_t bit)
  {
  return (0 != (data & (1 << bit)));
  }
/**
 * get_uint_bits: Get at unsigned integer values within data.
 * Compile-time defined section.
 */
template<uint8_t BIT, uint8_t BITLEN>
uint16_t get_uint_bits(uint32_t data)
  {
  return (data >> BIT) & ((0x1U <<(BITLEN+1))-1);
  }
/**
 * get_uint_bits: Get at unsigned integer values within data.
 * Run-time defined section.
 */
inline uint16_t get_uint_bits(uint32_t data, uint8_t bit, uint8_t bitlen)
  {
  return (data >> bit) & ((0x1U <<(bitlen+1))-1);
  }

/**
 * get_uint_bits: Get at signed integer values within data.
 * Compile-time defined section.
 */
template<uint8_t BIT, uint8_t BITLEN>
int16_t get_int_bits(uint32_t data)
  {
  uint16_t unsigned_val = get_uint_bits<BIT,BITLEN>(data);
  return sign_extend<uint16_t,int16_t, BITLEN-1>(unsigned_val);
  }
/**
 * get_uint_bits: Get at signed integer values within data.
 * Run-time defined section.
 */
inline int16_t get_int_bits(uint32_t data, uint8_t bit, uint8_t bitlen)
  {
  uint16_t unsigned_val = get_uint_bits(data, bit, bitlen);
  return sign_extend<uint16_t,int16_t>(unsigned_val, bitlen-1);
  }

// helper to provide a big endian integer from a given number of bytes.
// The helper function checks the length before using this.
template<uint8_t BYTES, typename UINT = uint32_t>
struct ovms_bytes_impl_t
  {
  static UINT get_bytes_uint(const uint8_t *data, uint32_t index)
    {
    return (data[index + (BYTES - 1)])
      | (ovms_bytes_impl_t < BYTES - 1, UINT>::get_bytes_uint(data, index) << 8);
    }
  };
template<typename UINT>
struct ovms_bytes_impl_t<1,UINT>
  {
  static UINT get_bytes_uint(const uint8_t  *data, uint32_t index)
    {
    return (data[index]);
    }
  };

// Helper Class to access Little-Endian values.
template<uint8_t BYTES, typename UINT = uint32_t>
struct get_bytes_le_impl_t
  {
  static UINT get_bytes_uint( const uint8_t *data, uint32_t index)
    {
    return ((data[index + (BYTES - 1)]) << (UINT(8) * (BYTES - 1)))
      | get_bytes_le_impl_t < BYTES - 1 >::get_bytes_uint(data, index);
    }
  };
template<typename UINT>
struct get_bytes_le_impl_t<1, UINT>
  {
  static UINT get_bytes_uint(const uint8_t *data, uint32_t index)
    {
    return (data[index]);
    }
  };

/**
 * get_uint_bytes_be: Access to unsigned integer (big-endian) in a data buffer.
 *
 * @return true If within bounds
 */
template<uint8_t BYTES, typename UINT = uint32_t>
bool get_uint_bytes_be(const uint8_t  *data, uint32_t index, uint32_t length, UINT &res)
{
  if ((index + (BYTES - 1)) >= length) {
    return false;
  }
  res = ovms_bytes_impl_t<BYTES,UINT>::get_bytes_uint(data, index);
  return true;
}

/**
 * get_int_bytes_be: Access to signed integer (big-endian) in a data buffer.
 * @return true If within bounds
 */
template<uint8_t BYTES, typename INT = int32_t>
bool get_int_bytes_be(const uint8_t  *data, uint32_t index, uint32_t length, INT &res)
  {
  if ((index + (BYTES - 1)) >= length)
    return false;
  typedef typename std::make_unsigned<INT>::type uint_t;

  uint_t uresult = ovms_bytes_impl_t<BYTES,uint_t>::get_bytes_uint(data, index);
  res = sign_extend<uint_t, INT, BYTES * 8 - 1>(uresult);
  return true;
  }

/**
 * get_uint_buff_be: Access to unsigned integer (big-endian) in a vector data buffer.
 * @return true If successful
 */
template<uint8_t BYTES, typename UINT = uint32_t>
bool get_uint_buff_be(const std::string &data, uint32_t index,  UINT &ures)
  {
  if ((index + (BYTES - 1)) >= data.size())
    return false;
  ures = ovms_bytes_impl_t<BYTES,UINT>::get_bytes_uint(reinterpret_cast<const uint8_t *>(data.data()), index);
  return true;
  }

/**
 * get_buff_int_be: Access to signed integer (big-endian) in a std::string data buffer.
 * @return true If successful
 */
template<uint8_t BYTES, typename INT = int32_t>
bool get_buff_int_be(const std::string &data, uint32_t index,  INT &res)
  {
  if ((index + (BYTES - 1)) >= data.size())
    return false;
  typedef typename std::make_unsigned<INT>::type uint_t;
  uint_t ures = ovms_bytes_impl_t<BYTES,uint_t>::get_bytes_uint(reinterpret_cast<const uint8_t *>(data.data()), index);

  res = sign_extend<uint_t, INT, BYTES * 8 - 1>(ures);
  return true;
  }

/**
 * get_bytes_uint_le: Access to unsigned integer (little-endian) in a data buffer.
 * @return true If within bounds
 */
template<uint8_t BYTES, typename UINT = uint32_t>
bool get_bytes_uint_le(const uint8_t *data, uint32_t index, uint32_t length, UINT &res)
  {
  if ((index + (BYTES - 1)) >= length)
    return false;
  res = get_bytes_le_impl_t<BYTES,UINT>::get_bytes_uint(data, index);
  return true;
  }

/** Access to unsigned integer (little-endian) in a vector data buffer.
 * @return true If within bounds
 */
template<uint8_t BYTES, typename UINT = uint32_t>
bool get_buff_uint_le(const std::string &data, uint32_t index, UINT &res)
  {
  if ((index + (BYTES - 1)) >= data.size())
    return false;
  res = get_bytes_le_impl_t<BYTES,UINT>::get_bytes_uint(reinterpret_cast<const uint8_t *>(data.data()), index);
  return true;
  }

/** Access to signed integer (little-endian) in a vector data buffer.
 * @return true If within bounds
 */
template<uint8_t BYTES, typename INT = int32_t>
bool get_buff_int_le(const std::string &data, uint32_t index, INT &res)
  {
  if ((index + (BYTES - 1)) >= data.size())
    return false;
  typedef typename std::make_unsigned<INT>::type uint_t;
  uint_t uresult = ovms_bytes_impl_t<BYTES,uint_t>::get_bytes_uint(reinterpret_cast<const uint8_t *>(data.data()), index);
  res = sign_extend < uint_t, INT, BYTES * 8 - 1 > (uresult);
  return true;
  }


/** Access a string portion in a vector data buffer.
 * @return true if start is within bounds.
 */ 
bool get_buff_string(const uint8_t *data, uint32_t size, uint32_t index, uint32_t len, std::string &strret);

inline bool get_buff_string(const std::string &data, uint32_t index, uint32_t len, std::string &strret)
  {
  return get_buff_string(reinterpret_cast<const uint8_t *>(data.data()), data.size(), index, len, strret);
  }

#endif // __OVMS_UTILS_H__
