/*
;    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 "ovms_log.h"
static const char *TAG = "version";

#include <esp_image_format.h>
#include <esp_ota_ops.h>
#include <esp_system.h>
#include "ovms.h"
#include "ovms_version.h"
#include "ovms_config.h"
#include "ovms_metrics.h"
#include "metrics_standard.h"
#include "ovms_events.h"
#include "ovms_peripherals.h"


std::string GetOVMSPartitionVersion(esp_partition_subtype_t t)
  {
  int i, len;
  size_t offset;
  size_t n, size;
  char *cp, *cp2, *buf2;
  std::string version;
  const esp_partition_t *p;
  esp_image_header_t *pfhdr;
  esp_image_segment_header_t *header;
  char buf[512 + 1];
  const char prefix[] = OVMS_VERSION_PREFIX;
  const char postfix[] = OVMS_VERSION_POSTFIX;

  /* Find the partition detail */
  p = esp_partition_find_first(ESP_PARTITION_TYPE_APP, t, NULL);
  if (p == NULL)
      return "";

  /* Read image header */
  pfhdr = (esp_image_header_t *)buf;
  offset = 0;
  if (esp_partition_read(p, offset, pfhdr, sizeof(*pfhdr)) != ESP_OK)
      return "";
  if (pfhdr->magic != ESP_IMAGE_HEADER_MAGIC)
      return "";
  if (pfhdr->segment_count > ESP_IMAGE_MAX_SEGMENTS)
      return "";

  /* Read the first segment header */
  header = (esp_image_segment_header_t *)buf;
  offset += sizeof(*pfhdr);
  if (esp_partition_read(p, offset, header, sizeof(*header)) != ESP_OK)
      return "";

  /* Search for the version string */
  offset += sizeof(*header);
  len = header->data_len;
  size = sizeof(buf) - 1;
  buf2 = buf;
  while (len > 0) {
    n = size;
    if (n > len)
      n = len;
    if (esp_partition_read(p, offset, buf2, n) != ESP_OK)
      return "";
    offset += n;

    /* Insure EOS */
    buf2[n] = '\0';
    len -= n;

    n = sizeof(buf) - (sizeof(prefix) + sizeof(postfix) - 2);
    for (i = 0, cp = buf; i < n; ++i, ++cp) {
      if (*cp != prefix[0])
        continue;
      if (strncmp(cp, prefix, sizeof(prefix) - 1) != 0)
	continue;
      cp2 = strstr(cp, postfix);
      if (cp2 == NULL)
	continue;
      *cp2 = '\0';
      cp += sizeof(prefix) - 1;
      *cp2 = '\0';
      version.assign(cp);
      return version;
    }

    /* Shift the buffer left to avoid splitting the string */
    n = (sizeof(buf) - 1) / 2;
    buf2 = buf + n;
    memcpy(buf, buf2, n);
    size = n;
  }
  return "";
  }

std::string GetOVMSVersion()
  {
  std::string searchversion(OVMS_VERSION_PREFIX OVMS_VERSION OVMS_VERSION_POSTFIX);
  std::string version(OVMS_VERSION);
  std::string tag = MyConfig.GetParamValue("ota","tag");

  const esp_partition_t *p = esp_ota_get_running_partition();
  if (p != NULL)
    {
    version.append("/");
    version.append(p->label);
    }

  version.append("/");
  if (tag.empty())
    version.append(CONFIG_OVMS_VERSION_TAG);
  else
    version.append(tag);

  return version;
  }

std::string GetOVMSBuild()
  {
  std::string build("idf ");
  build.append(esp_get_idf_version());
  build.append(" ");
  build.append(__DATE__);
  build.append(" ");
  build.append(__TIME__);

  return build;
  }

std::string GetOVMSProduct()
  {
  #ifdef CONFIG_OVMS_HW_BASE_3_0
  return std::string("v3.0");
  #endif //#ifdef CONFIG_OVMS_HW_BASE_3_0

  #ifdef CONFIG_OVMS_HW_BASE_3_1
  esp_chip_info_t chip;
  esp_chip_info(&chip);
  if (chip.revision < 3)
    { return std::string("v3.1"); }
  else
    { return std::string("v3.3"); }
  #endif //#ifdef CONFIG_OVMS_HW_BASE_3_1
  }

std::string GetOVMSHardware()
  {
  std::string hardware("OVMS ");
  esp_chip_info_t chip;
  esp_chip_info(&chip);
  if (chip.features & CHIP_FEATURE_EMB_FLASH) hardware.append("EMBFLASH ");
  if (chip.features & CHIP_FEATURE_WIFI_BGN) hardware.append("WIFI ");
  if (chip.features & CHIP_FEATURE_BLE) hardware.append("BLE ");
  if (chip.features & CHIP_FEATURE_BT) hardware.append("BT ");
  char buf[32]; sprintf(buf,"cores=%d ",chip.cores); hardware.append(buf);
  sprintf(buf,"rev=ESP32/%d",chip.revision); hardware.append(buf);

#ifdef CONFIG_OVMS_COMP_CELLULAR
  if (MyPeripherals->m_cellular_modem && MyPeripherals->m_cellular_modem->m_model != "auto")
    {
    hardware.append("; MODEM ");
    hardware.append(MyPeripherals->m_cellular_modem->m_model);
    }
#endif

  return hardware;
  }

void Version(std::string event, void* data)
  {
//  char buf[20];
//  uint8_t mac[6];

  std::string metric = GetOVMSVersion();
  metric.append(" (build ");
  metric.append(GetOVMSBuild());
  metric.append(")");
  StandardMetrics.ms_m_version->SetValue(metric.c_str());

  metric = GetOVMSHardware();
  StandardMetrics.ms_m_hardware->SetValue(metric.c_str());

//  esp_efuse_mac_get_default(mac);
//  sprintf(buf,"%02x:%02x:%02x:%02x:%02x:%02x",
//          mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
//  StandardMetrics.ms_m_serial->SetValue(buf);
  }

class VersionInit
  {
  public: VersionInit();
} MyVersionInit  __attribute__ ((init_priority (9900)));

VersionInit::VersionInit()
  {
  ESP_LOGI(TAG, "Initialising Versioning (9900)");

  #undef bind  // Kludgy, but works
  using std::placeholders::_1;
  using std::placeholders::_2;
  MyEvents.RegisterEvent(TAG,"system.start", std::bind(&Version, _1, _2));
  MyEvents.RegisterEvent(TAG,"system.modem.installed", std::bind(&Version, _1, _2));
  }
