/*
;    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.
*/

#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fstream>
#include "ovms_utils.h"
#include "ovms_config.h"
#include "ovms_events.h"
#include "metrics_standard.h"
#include "ovms_version.h"

/**
 * chargestate_code: convert legacy chargestate key to code
 */
std::string chargestate_code(const int key)
  {
  std::string code;
  switch (key)
    {
    case  1: code = "charging";   break;
    case  2: code = "topoff";     break;
    case  4: code = "done";       break;
    case 13: code = "prepare";    break;
    case 14: code = "timerwait";  break;
    case 15: code = "heating";    break;
    case 21: code = "stopped";    break;
    default: code = "";
    }
  return code;
  }

/**
 * chargestate_key: convert chargestate code to legacy key
 */
int chargestate_key(const std::string code)
  {
  int key;
  if      (code == "charging")    key = 1;
  else if (code == "topoff")      key = 2;
  else if (code == "done")        key = 4;
  else if (code == "prepare")     key = 13;
  else if (code == "timerwait")   key = 14;
  else if (code == "heating")     key = 15;
  else if (code == "stopped")     key = 21;
  else key = 0;
  return key;
  }

/**
 * chargesubstate_code: convert legacy charge substate key to code
 */
std::string chargesubstate_code(const int key)
  {
  std::string code;
  switch (key)
    {
    case 0x01: code = "scheduledstop";    break;
    case 0x02: code = "scheduledstart";   break;
    case 0x03: code = "onrequest";        break;
    case 0x05: code = "timerwait";        break;
    case 0x07: code = "powerwait";        break;
    case 0x0d: code = "stopped";          break;
    case 0x0e: code = "interrupted";      break;
    default: code = "";
    }
  return code;
  }

/**
 * chargesubstate_key: convert charge substate code to legacy key
 */
int chargesubstate_key(const std::string code)
  {
  int key;
  if      (code == "scheduledstop")       key = 0x01;
  else if (code == "scheduledstart")      key = 0x02;
  else if (code == "onrequest")           key = 0x03;
  else if (code == "timerwait")           key = 0x05;
  else if (code == "powerwait")           key = 0x07;
  else if (code == "stopped")             key = 0x0d;
  else if (code == "interrupted")         key = 0x0e;
  else key = 0;
  return key;
  }

/**
 * chargemode_code: convert legacy chargemode key to code
 */
std::string chargemode_code(const int key)
  {
  std::string code;
  switch (key)
    {
    case 0: code = "standard";      break;
    case 1: code = "storage";       break;
    case 3: code = "range";         break;
    case 4: code = "performance";   break;
    default: code = "";
    }
  return code;
  }

/**
 * chargemode_key: convert chargemode code to legacy key
 */
int chargemode_key(const std::string code)
  {
  int key;
  if      (code == "standard")      key = 0;
  else if (code == "storage")       key = 1;
  else if (code == "range")         key = 3;
  else if (code == "performance")   key = 4;
  else key = -1;
  return key;
  }

/**
 * 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)
  {
  std::string res;
  char lc = 0;
  res.reserve(text.length());
  for (int i=0; i<text.length(); i++)
    {
    if (text[i] == '\n')
      {
      if (lc != '\r')
        res += '\r';
      }
    else if (text[i] == ',')
      {
      res += ';';
      }
    else
      {
      res += text[i];
      }

    lc = text[i];
    }
  return res;
  }

/**
 * mp_encode: encode string for MP transport;
 *  - replace '\r\n' by '\r'
 *  - replace '\n' by '\r'
 *  - replace ',' by ';'
 */
extram::string mp_encode(const extram::string text)
  {
  extram::string res;
  char lc = 0;
  res.reserve(text.length());
  for (int i=0; i<text.length(); i++)
    {
    if (text[i] == '\n')
      {
      if (lc != '\r')
        res += '\r';
      }
    else if (text[i] == ',')
      {
      res += ';';
      }
    else
      {
      res += text[i];
      }

    lc = text[i];
    }
  return res;
  }

/**
 * stripcr:
 *  - replace '\r\n' by '\n'
 */
extram::string stripcr(const extram::string& text)
  {
  extram::string res;
  res.reserve(text.length());
  for (int i = 0; i < text.length(); i++)
    {
    if (text[i] != '\r' || (i < text.length()-1 && text[i+1] != '\n'))
      res += text[i];
    }
  return res;
  }

/**
 * stripesc: remove terminal escape sequences from (log) string
 */
std::string stripesc(const char* s)
  {
  std::string res;
  res.reserve(200);
  bool skip = false;
  while (s && *s)
    {
    if (*s == '\033' && *(s+1) == '[')
      skip = true;
    else if (!skip)
      res += *s;
    else if (*s == 'm')
      skip = false;
    ++s;
    }
  return res;
  }

/**
 * 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)
  {
  uint8_t nibble;

  nibble = byte >> 4;   // high nibble
  nibble += '0'; if (nibble>'9') nibble += 39;
  *p = nibble; p++;

  nibble = byte & 0x0f; // low nibble
  nibble += '0'; if (nibble>'9') nibble += 39;
  *p = nibble; p++;
  return p;
  }

/**
 * 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*/)
  {
  const char *s = data;

  if (rlength>0)
    {
    if (!*bufferp)
      *bufferp = (char*) ExternalRamMalloc(colsize*4 + 4); // space for 16x3 + 2 + 16 + 1(\0)

    char *p = *bufferp;
    const char *os = s;
    for (int k=0;k<colsize;k++)
      {
      if (k<rlength)
        {
        sprintf(p,"%2.2x ",*s);
        s++;
        }
      else
        {
        sprintf(p,"   ");
        }
      p+=3;
      }
    sprintf(p,"| ");
    p += 2;
    s = os;
    for (int k=0;k<colsize;k++)
      {
      if (k<rlength)
        {
        if (isprint((int)*s))
          { *p = *s; }
        else
          { *p = '.'; }
        s++;
        }
      else
        {
        *p = ' ';
        }
      p++;
      }
    *p = 0;
    if (rlength > colsize)
      rlength -= colsize;
    else
      rlength = 0;
    }

  return rlength;
  }


/**
 * hexencode: encode a string of bytes into hexadecimal form
 */
std::string hexencode(const std::string value)
  {
  std::string encval;
  size_t len = value.length();
  encval.reserve(len * 2);
  char buf[3] = {0};
  for (size_t i = 0; i < len; ++i)
    {
    unsigned char c = value.at(i);
    HexByte(buf, c);
    encval += buf;
    }
  return encval;
  }


/**
 * hexdecode: decode a hexadecimal encoded string of bytes
 *  Returns empty string on error
 */
std::string hexdecode(const std::string encval)
  {
  std::string value;
  size_t len = encval.length();
  if (len == 0)
    return value;
  if (encval.find_first_not_of("0123456789ABCDEFabcdef", 0) != std::string::npos || (len & 1))
    return value;
  value.reserve(len / 2);
  char buf[3] = {0};
  for (size_t i = 0; i < len; i += 2)
    {
    buf[0] = encval.at(i);
    buf[1] = encval.at(i + 1);
    unsigned char c = strtoul(buf, NULL, 16);
    value += c;
    }
  return value;
  }


/**
 * 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)
  {
  const char cs1[] = "abcdefghijklmnopqrstuvwxyz";
  const char cs2[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  const char cs3[] = "!@#$%^&*()_-=+;:,.?";
  std::string res;
  for (int i=0; i < length; i++)
    {
    double r = drand48();
    if (r > 0.4)
      res.push_back((char)cs1[(int)(drand48()*(sizeof(cs1)-1))]);
    else if (r > 0.2)
      res.push_back((char)cs2[(int)(drand48()*(sizeof(cs2)-1))]);
    else
      res.push_back((char)cs3[(int)(drand48()*(sizeof(cs3)-1))]);
    }
  return res;
  }


#ifdef CONFIG_FREERTOS_USE_TRACE_FACILITY
/**
 * TaskGetHandle: get task handle by name
 * (FreeRTOS xTaskGetHandle() is not available)
 */
TaskHandle_t TaskGetHandle(const char *name)
  {
  TaskHandle_t res = 0;
  TaskStatus_t* pxTaskStatusArray;
  volatile UBaseType_t uxArraySize, x;

  uxArraySize = uxTaskGetNumberOfTasks();
  pxTaskStatusArray = (TaskStatus_t*)pvPortMalloc(uxArraySize * sizeof(TaskStatus_t));
  if (pxTaskStatusArray != NULL)
    {
    uxArraySize = uxTaskGetSystemState(pxTaskStatusArray, uxArraySize, NULL);
    for (x = 0; x < uxArraySize; x++)
      {
      if (strcmp(pxTaskStatusArray[x].pcTaskName, name) == 0)
        {
        res = pxTaskStatusArray[x].xHandle;
        break;
        }
      }
    vPortFree( pxTaskStatusArray );
    }
  return res;
  }
#endif // CONFIG_FREERTOS_USE_TRACE_FACILITY


/**
 * mkpath: mkdir -p
 *
 * Original: https://stackoverflow.com/a/12904145
 */
int mkpath(std::string path, mode_t mode /*=0*/)
  {
  size_t pre = 0, pos;
  std::string dir;
  int mdret;

  if (!endsWith(path, '/'))
    {
    // force trailing / so we can handle everything in loop
    path.append("/");
    }

  while ((pos = path.find_first_of('/', pre)) != std::string::npos)
    {
    dir = path.substr(0, pos++);
    pre = pos;
    if (dir.size() == 0)
      continue; // if leading / first time is 0 length
    if ((mdret = mkdir(dir.c_str(), mode)) && errno != EEXIST)
      return mdret;
    }

  return 0;
  }

/**
 * rmtree: rmdir -r
 */
int rmtree(const std::string path)
  {
  DIR *dir = opendir(path.c_str());
  if (!dir)
    return 0;

  struct dirent *dp;
  struct stat st;
  std::string sub;
  bool ok = true;

  while ((dp = readdir(dir)) != NULL) {
    sub = path + "/" + dp->d_name;
    if (stat(sub.c_str(), &st)) {
      ok = false;
      break;
    }
    if (S_ISDIR(st.st_mode))
      ok = (rmtree(sub) == 0);
    else
      ok = (unlink(sub.c_str()) == 0);
    if (!ok)
      break;
  }

  closedir(dir);
  ok = (rmdir(path.c_str()) == 0);
  return ok ? 0 : -1;
  }

/**
 * path_exists: check if filesystem path exists
 */
bool path_exists(const std::string path)
  {
  struct stat st;
  return (stat(path.c_str(), &st) == 0);
  }

/**
 * load file into string:
 *  - return value: 0 = ok / errno
 */
int load_file(const std::string &path, extram::string &content)
  {
  std::ifstream file(path, std::ios::in | std::ios::binary | std::ios::ate);
  if (file.is_open())
    {
    auto size = file.tellg();
    if (size > 0)
      {
      content.resize(size, '\0');
      file.seekg(0);
      file.read(&content[0], size);
      }
    }
  if (file.fail())
    return errno;
  else
    return 0;
  }

/**
 * save file from string:
 *  - creates missing directories automatically & signals system.vfs.file.changed
 *  - return value: 0 = ok / errno
 */
int save_file(const std::string &path, extram::string &content)
  {
  // create path:
  size_t n = path.rfind('/');
  if (n != 0 && n != std::string::npos)
    {
    std::string dir = path.substr(0, n);
    if (!path_exists(dir))
      {
      if (mkpath(dir) != 0)
        return errno;
      }
    }

  // write file:
  std::ofstream file(path, std::ios::out | std::ios::binary | std::ios::trunc);
  if (file.is_open())
    file.write(content.data(), content.size());
  if (file.fail())
    {
    return errno;
    }
  else
    {
    MyEvents.SignalEvent("system.vfs.file.changed", (void*)path.c_str(), path.size()+1);
    return 0;
    }
  }


/**
 * mqtt_topic: convert dotted string (e.g. notification subtype) to MQTT topic
 *  - replace '.' by '/'
 */
std::string mqtt_topic(const std::string text)
  {
  std::string buf;
  buf.reserve(text.size());
  for (int i=0; i<text.size(); i++)
    {
    switch(text[i])
      {
      case '.':         buf += '/'; break;
      default:          buf += text[i]; break;
      }
    }
	return buf;
  }

/**
 * 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()
  {
  std::string ua;
  ua = "ovms/";
  ua.append(GetOVMSProduct());
  ua.append(" (");
  ua.append(MyConfig.GetParamValue("vehicle","id",""));
  ua.append(" ");
  ua.append(StandardMetrics.ms_m_version->AsString());
  ua.append(")");
  return ua;
  }

/**
 * float2double: minimize precision errors on float→double conversion
 *  Casting a float to double sets the additional precision bits to 0, resulting in
 *  the double to have a significant offset from the rounded float; e.g.
 *  11.08 becomes 11.079999923706055.
 *  (Is there a better implementation for this than sprintf/atof?)
 */
double float2double(float f)
  {
  char buf[16];
  snprintf(buf, sizeof buf, "%g", f);
  return atof(buf);
  }

/**
 * idtag: create object instance tag for registrations
 */
std::string idtag(const char* tag, void* instance)
  {
  std::ostringstream buf;
  buf << tag << "-" << instance;
  std::string res = buf.str();
  return res;
  }

/**
 * get_buff_string: Helper function to get at string value from sized buffer.
 */
bool get_buff_string(const uint8_t *data, uint32_t size, uint32_t index, uint32_t len, std::string &strret)
  {
  int remain = size - index;
  if (remain < 0)
    return false;
  if (remain < len)
    len = remain;
  const char *begin = reinterpret_cast<const char *>(data + index) ;
  const char *end   = begin + len;
  strret.assign<const char *>(begin, end);
  return true;
  }
