/*
;    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 = "vehicle";

#include <stdio.h>
#include <algorithm>
#include <ovms_command.h>
#include <ovms_script.h>
#include <ovms_metrics.h>
#include <ovms_notify.h>
#include <metrics_standard.h>
#ifdef CONFIG_OVMS_COMP_WEBSERVER
#include <ovms_webserver.h>
#endif // #ifdef CONFIG_OVMS_COMP_WEBSERVER
#include <ovms_peripherals.h>
#include <string_writer.h>
#include "vehicle.h"


/**
 * PollerStateTicker: check for state changes (stub, override with vehicle implementation)
 *  This is called by VehicleTicker1() just before the next PollerSend().
 *  Implement your poller state transition logic in this method, so the changes
 *  will get applied immediately.
 */
void OvmsVehicle::PollerStateTicker()
  {
  }


/**
 * IncomingPollReply: poll response handler (stub, override with vehicle implementation)
 *  This is called by PollerReceive() on each valid response frame for the current request.
 *  Be aware responses may consist of multiple frames, detectable e.g. by mlremain > 0.
 *  A typical pattern is to collect frames in a buffer until mlremain == 0.
 *  
 *  @param bus
 *    CAN bus the current poll is done on
 *  @param type
 *    OBD2 mode / UDS polling type, e.g. VEHICLE_POLL_TYPE_READDTC
 *  @param pid
 *    PID addressed (depending on the request type, may be none / 8 bit / 16 bit)
 *  @param data
 *    Payload
 *  @param length
 *    Payload size
 *  @param mlremain
 *    Remaining bytes expected to complete the response after this frame
 *  
 *  @member m_poll_moduleid_sent
 *    The CAN ID addressed by the current request (txmoduleid)
 *  @member m_poll_ml_frame
 *    Frame number of the response, 0 = first frame / new response
 *  @member m_poll_ml_offset
 *    Byte position of this frame's payload part in the response, 0 = first frame
 *  @member m_poll_plcur
 *    Pointer to the currently processed poll entry
 */
void OvmsVehicle::IncomingPollReply(canbus* bus, uint16_t type, uint16_t pid, uint8_t* data, uint8_t length, uint16_t mlremain)
  {
  }


/**
 * IncomingPollError: poll response error handler (stub, override with vehicle implementation)
 *  This is called by PollerReceive() on reception of an OBD/UDS Negative Response Code (NRC),
 *  except if the code is requestCorrectlyReceived-ResponsePending (0x78), which is handled
 *  by the poller. See ISO 14229 Annex A.1 for the list of NRC codes.
 *  
 *  @param bus
 *    CAN bus the current poll is done on
 *  @param type
 *    OBD2 mode / UDS polling type, e.g. VEHICLE_POLL_TYPE_READDTC
 *  @param pid
 *    PID addressed (depending on the request type, may be none / 8 bit / 16 bit)
 *  @param code
 *    NRC detail code
 *  
 *  @member m_poll_moduleid_sent
 *    The CAN ID addressed by the current request (txmoduleid)
 *  @member m_poll_plcur
 *    Pointer to the currently processed poll entry
 */
void OvmsVehicle::IncomingPollError(canbus* bus, uint16_t type, uint16_t pid, uint16_t code)
  {
  }


/**
 * IncomingPollTxCallback: poller TX callback (stub, override with vehicle implementation)
 *  This is called by PollerTxCallback() on TX success/failure for a poller request.
 *  You can use this to detect CAN bus issues, e.g. if the car switches off the OBD port.
 *  
 *  ATT: this is executed in the main CAN task context. Keep it simple.
 *    Complex processing here will affect overall CAN performance.
 *  
 *  @param bus
 *    CAN bus the current poll is done on
 *  @param txid
 *    The module TX ID of the current poll
 *  @param type
 *    OBD2 mode / UDS polling type, e.g. VEHICLE_POLL_TYPE_READDTC
 *  @param pid
 *    PID addressed (depending on the request type, may be none / 8 bit / 16 bit)
 *  @param success
 *    Frame transmission success
 */
void OvmsVehicle::IncomingPollTxCallback(canbus* bus, uint32_t txid, uint16_t type, uint16_t pid, bool success)
  {
  }


/**
 * PollSetPidList: set the default bus and the polling list to process
 *  Call this to install a new polling list or restart the list.
 *  This won't change the polling state; you can change the list while keeping the state.
 *  The list is changed without waiting for pending responses to finish (except PollSingleRequests).
 *  
 *  @param bus
 *    CAN bus to use as the default bus (for all poll entries with bus=0) or NULL to stop polling
 *  @param plist
 *    The polling list to use or NULL to stop polling
 */
void OvmsVehicle::PollSetPidList(canbus* bus, const poll_pid_t* plist)
  {
  OvmsRecMutexLock slock(&m_poll_single_mutex);
  OvmsRecMutexLock lock(&m_poll_mutex);
  m_poll_bus = bus;
  m_poll_bus_default = bus;
  m_poll_plist = plist;
  m_poll_ticker = 0;
  m_poll_sequence_cnt = 0;
  m_poll_wait = 0;
  m_poll_plcur = NULL;
  m_poll_entry = {};
  m_poll_txmsgid = 0;
  }


/**
 * PollSetState: set the polling state
 *  Call this to change the polling state and restart the current polling list.
 *  This won't do anything if the state is already active. The state is changed without
 *  waiting for pending responses to finish (except PollSingleRequests).
 *  
 *  @param state
 *    The polling state to activate (0 … VEHICLE_POLL_NSTATES)
 */
void OvmsVehicle::PollSetState(uint8_t state)
  {
  if ((state < VEHICLE_POLL_NSTATES)&&(state != m_poll_state))
    {
    OvmsRecMutexLock slock(&m_poll_single_mutex);
    OvmsRecMutexLock lock(&m_poll_mutex);
    m_poll_state = state;
    m_poll_ticker = 0;
    m_poll_sequence_cnt = 0;
    m_poll_wait = 0;
    m_poll_plcur = NULL;
    m_poll_entry = {};
    m_poll_txmsgid = 0;
    }
  }


/**
 * PollSetThrottling: configure polling speed / niceness
 *  If multiple requests are due at the same poll tick (second), this controls how many of
 *  them will be sent in series without a delay, i.e. as soon as the response/timeout for
 *  the previous request occurred.
 *  
 *  @param sequence_max
 *    Polls allowed to be sent in sequence per time tick (second), default 1, 0 = no limit.
 *  
 *  The configuration is kept unchanged over calls to PollSetPidList() or PollSetState().
 */
void OvmsVehicle::PollSetThrottling(uint8_t sequence_max)
  {
  OvmsRecMutexLock lock(&m_poll_mutex);
  m_poll_sequence_max = sequence_max;
  }


/**
 * PollSetResponseSeparationTime: configure ISO TP multi frame response timing
 *  See: https://en.wikipedia.org/wiki/ISO_15765-2
 *  
 *  @param septime
 *    Separation Time (ST), the minimum delay time between frames. Default: 25 milliseconds
 *    ST values up to 127 (0x7F) specify the minimum number of milliseconds to delay between frames,
 *    while values in the range 241 (0xF1) to 249 (0xF9) specify delays increasing from
 *    100 to 900 microseconds.
 *  
 *  The configuration is kept unchanged over calls to PollSetPidList() or PollSetState().
 */
void OvmsVehicle::PollSetResponseSeparationTime(uint8_t septime)
  {
  assert (septime <= 127 || (septime >= 241 && septime <= 249));
  OvmsRecMutexLock lock(&m_poll_mutex);
  m_poll_fc_septime = septime;
  }


/**
 * PollSetChannelKeepalive: configure keepalive timeout for channel oriented protocols
 * 
 *  The VWTP poller will keep a channel connection to a module open for this
 *  many seconds after the last poll data transmission has taken place, to
 *  minimize connection overhead in case the next poll is sent to the same
 *  module.
 * 
 *  Devices may stay awake as long as the channel is open, so don't disable
 *  the timeout except for special purposes.
 * 
 *  @param keepalive_seconds
 *    Channel inactivity timeout in seconds, 0 = disable, default/init = 60
 */
void OvmsVehicle::PollSetChannelKeepalive(uint16_t keepalive_seconds)
  {
  m_poll_ch_keepalive = keepalive_seconds;
  }


/**
 * PollerSend: internal: start next due request
 */
void OvmsVehicle::PollerSend(bool fromTicker)
  {
  OvmsRecMutexLock lock(&m_poll_mutex);

  // ESP_LOGD(TAG, "PollerSend(%d): entry at[type=%02X, pid=%X], ticker=%u, wait=%u, cnt=%u/%u",
  //          fromTicker, m_poll_plcur->type, m_poll_plcur->pid,
  //          m_poll_ticker, m_poll_wait, m_poll_sequence_cnt, m_poll_sequence_max);

  if (fromTicker)
    {
    // Timer ticker call: reset throttling counter, check response timeout
    m_poll_sequence_cnt = 0;
    if (m_poll_wait > 0) m_poll_wait--;

    // Protocol specific ticker calls:
    PollerVWTPTicker();
    }
  if (m_poll_wait > 0) return;

  // Check poll bus & list:
  if (!m_poll_bus_default || !m_poll_plist || m_poll_plist->txmoduleid == 0) return;

  // Restart poll list cursor:
  if (m_poll_plcur == NULL) m_poll_plcur = m_poll_plist;

  m_poll_entry = {};

  while (m_poll_plcur->txmoduleid != 0)
    {
    if ((m_poll_plcur->polltime[m_poll_state] > 0) &&
        ((m_poll_ticker % m_poll_plcur->polltime[m_poll_state]) == 0))
      {
      // We need to poll this one...
      m_poll_entry = *m_poll_plcur;
      m_poll_protocol = m_poll_plcur->protocol;
      m_poll_type = m_poll_plcur->type;
      m_poll_pid = m_poll_plcur->pid;

      switch (m_poll_plcur->pollbus)
        {
        case 1:
          m_poll_bus = m_can1;
          break;
        case 2:
          m_poll_bus = m_can2;
          break;
        case 3:
          m_poll_bus = m_can3;
          break;
        case 4:
          m_poll_bus = m_can4;
          break;
        default:
          m_poll_bus = m_poll_bus_default;
        }

      // Dispatch transmission start to protocol handler:
      if (m_poll_protocol == VWTP_20)
        PollerVWTPStart(fromTicker);
      else
        PollerISOTPStart(fromTicker);

      m_poll_plcur++;
      m_poll_sequence_cnt++;
      return;
      }

    // Poll entry is not due, check next
    m_poll_plcur++;
    }

  // Completed checking all poll entries for the current m_poll_ticker
  // ESP_LOGD(TAG, "PollerSend(%d): cycle complete for ticker=%u", fromTicker, m_poll_ticker);
  m_poll_plcur = m_poll_plist;
  m_poll_ticker++;
  if (m_poll_ticker > 3600) m_poll_ticker -= 3600;
  }


/**
 * PollerTxCallback: internal: process poll request callbacks
 */
void OvmsVehicle::PollerTxCallback(const CAN_frame_t* frame, bool success)
  {
  OvmsRecMutexLock lock(&m_poll_mutex);

  // Check for a late callback:
  if (!m_poll_wait || !m_poll_plist || frame->origin != m_poll_bus || frame->MsgID != m_poll_txmsgid)
    return;

  // Forward to protocol handler:
  if (m_poll_protocol == VWTP_20)
    PollerVWTPTxCallback(frame, success);

  // On failure, try to speed up the current poll timeout:
  if (!success)
    {
    m_poll_wait = 0;
    if (m_poll_single_rxbuf)
      {
      m_poll_single_rxerr = POLLSINGLE_TXFAILURE;
      m_poll_single_rxbuf = NULL;
      m_poll_single_rxdone.Give();
      }
    }

  // Forward to application:
  IncomingPollTxCallback(m_poll_bus, m_poll_moduleid_sent, m_poll_type, m_poll_pid, success);
  }


/**
 * PollerReceive: internal: process poll response frame
 */
void OvmsVehicle::PollerReceive(CAN_frame_t* frame, uint32_t msgid)
  {
  // obsolete
  }


/**
 * PollSingleRequest: perform prioritized synchronous single OBD2/UDS request
 *  Pass a full OBD2/UDS request (mode/type, PID, additional payload).
 *  The request is sent immediately, aborting a running poll list request. The previous
 *  poller state is automatically restored after the request has been performed, but
 *  without any guarantee for repetition or omission of an aborted poll.
 *  On success, the response buffer will contain the response payload (may be empty).
 *  
 *  See OvmsVehicleFactory::obdii_request() for a usage example.
 *  
 *  ATT: must not be called from within the vehicle task context -- deadlock situation!
 *  
 *  @param bus          CAN bus to use for the request
 *  @param txid         CAN ID to send to (0x7df = broadcast)
 *  @param rxid         CAN ID to expect response from (broadcast: 0)
 *  @param request      Request to send (binary string) (type + pid + up to 4095 bytes payload)
 *  @param response     Response buffer (binary string) (multiple response frames assembled)
 *  @param timeout_ms   Timeout for poller/response in milliseconds
 *  @param protocol     Protocol variant: ISOTP_STD / ISOTP_EXTADR / ISOTP_EXTFRAME
 *  
 *  @return             POLLSINGLE_OK         (0)   -- success, response is valid
 *                      POLLSINGLE_TIMEOUT    (-1)  -- timeout/poller unavailable
 *                      POLLSINGLE_TXFAILURE  (-2)  -- CAN transmission failure
 *                      else                  (>0)  -- UDS NRC detail code
 *                      Note: response is only valid with return value 0
 */
int OvmsVehicle::PollSingleRequest(canbus* bus, uint32_t txid, uint32_t rxid,
                                   std::string request, std::string& response,
                                   int timeout_ms /*=3000*/, uint8_t protocol /*=ISOTP_STD*/)
  {
  if (!m_ready)
    return -1;

  if (!m_registeredlistener)
    {
    m_registeredlistener = true;
    MyCan.RegisterListener(m_rxqueue);
    }

  OvmsRecMutexLock slock(&m_poll_single_mutex, pdMS_TO_TICKS(timeout_ms));
  if (!slock.IsLocked())
    return -1;

  // prepare single poll:
  OvmsVehicle::poll_pid_t poll[] =
    {
      { txid, rxid, 0, 0, { 999, 999, 999, 999 }, 0, protocol },
      POLL_LIST_END
    };

  assert(request.size() > 0);
  poll[0].type = request[0];
  poll[0].xargs.tag = POLL_TXDATA;

  if (POLL_TYPE_HAS_16BIT_PID(poll[0].type))
    {
    assert(request.size() >= 3);
    poll[0].xargs.pid = request[1] << 8 | request[2];
    poll[0].xargs.datalen = LIMIT_MAX(request.size()-3, 4095);
    poll[0].xargs.data = (const uint8_t*)request.data()+3;
    }
  else if (POLL_TYPE_HAS_8BIT_PID(poll[0].type))
    {
    assert(request.size() >= 2);
    poll[0].xargs.pid = request.at(1);
    poll[0].xargs.datalen = LIMIT_MAX(request.size()-2, 4095);
    poll[0].xargs.data = (const uint8_t*)request.data()+2;
    }
  else
    {
    poll[0].xargs.pid = 0;
    poll[0].xargs.datalen = LIMIT_MAX(request.size()-1, 4095);
    poll[0].xargs.data = (const uint8_t*)request.data()+1;
    }

  // acquire poller access:
  if (!m_poll_mutex.Lock(pdMS_TO_TICKS(timeout_ms)))
    return -1;

  // save poller state:
  canbus*           p_bus    = m_poll_bus_default;
  const poll_pid_t* p_list   = m_poll_plist;
  const poll_pid_t* p_plcur  = m_poll_plcur;
  uint32_t          p_ticker = m_poll_ticker;

  // start single poll:
  PollSetPidList(bus, poll);
  m_poll_single_rxdone.Take(0);
  m_poll_single_rxbuf = &response;
  PollerSend(true);
  m_poll_mutex.Unlock();

  // wait for response:
  bool rxok = m_poll_single_rxdone.Take(pdMS_TO_TICKS(timeout_ms));

  // restore poller state:
  m_poll_mutex.Lock();
  PollSetPidList(p_bus, p_list);
  m_poll_plcur = p_plcur;
  m_poll_ticker = p_ticker;
  m_poll_single_rxbuf = NULL;
  m_poll_mutex.Unlock();

  return (rxok == pdFALSE) ? -1 : m_poll_single_rxerr;
  }


/**
 * PollSingleRequest: perform prioritized synchronous single OBD2/UDS request
 *  Convenience wrapper for standard PID polls, see above for main implementation.
 *  
 *  @param bus          CAN bus to use for the request
 *  @param txid         CAN ID to send to (0x7df = broadcast)
 *  @param rxid         CAN ID to expect response from (broadcast: 0)
 *  @param polltype     OBD2/UDS poll type …
 *  @param pid          … and PID to poll
 *  @param response     Response buffer (binary string) (multiple response frames assembled)
 *  @param timeout_ms   Timeout for poller/response in milliseconds
 *  @param protocol     Protocol variant: ISOTP_STD / ISOTP_EXTADR / ISOTP_EXTFRAME
 *  
 *  @return             POLLSINGLE_OK         ( 0)  -- success, response is valid
 *                      POLLSINGLE_TIMEOUT    (-1)  -- timeout/poller unavailable
 *                      POLLSINGLE_TXFAILURE  (-2)  -- CAN transmission failure
 *                      else                  (>0)  -- UDS NRC detail code
 *                      Note: response is only valid with return value 0
 */
int OvmsVehicle::PollSingleRequest(canbus* bus, uint32_t txid, uint32_t rxid,
                                   uint8_t polltype, uint16_t pid, std::string& response,
                                   int timeout_ms /*=3000*/, uint8_t protocol /*=ISOTP_STD*/)
  {
  std::string request;
  request = (char) polltype;
  if (POLL_TYPE_HAS_16BIT_PID(polltype))
    {
    request += (char) (pid >> 8);
    request += (char) (pid & 0xff);
    }
  else if (POLL_TYPE_HAS_8BIT_PID(polltype))
    {
    request += (char) (pid & 0xff);
    }
  return PollSingleRequest(bus, txid, rxid, request, response, timeout_ms, protocol);
  }


/**
 * PollResultCodeName: get text representation of result code
 */
const char* OvmsVehicle::PollResultCodeName(int code)
  {
  switch (code)
    {
    case 0x10:  return "generalReject";
    case 0x11:  return "serviceNotSupported";
    case 0x12:  return "subFunctionNotSupported";
    case 0x13:  return "incorrectMessageLengthOrInvalidFormat";
    case 0x14:  return "responseTooLong";
    case 0x21:  return "busyRepeatRequest";
    case 0x22:  return "conditionsNotCorrect";
    case 0x24:  return "requestSequenceError";
    case 0x25:  return "noResponseFromSubnetComponent";
    case 0x26:  return "failurePreventsExecutionOfRequestedAction";
    case 0x31:  return "requestOutOfRange";
    case 0x33:  return "securityAccessDenied";
    case 0x35:  return "invalidKey";
    case 0x36:  return "exceedNumberOfAttempts";
    case 0x37:  return "requiredTimeDelayNotExpired";
    case 0x70:  return "uploadDownloadNotAccepted";
    case 0x71:  return "transferDataSuspended";
    case 0x72:  return "generalProgrammingFailure";
    case 0x73:  return "wrongBlockSequenceCounter";
    case 0x78:  return "requestCorrectlyReceived-ResponsePending";
    case 0x7e:  return "subFunctionNotSupportedInActiveSession";
    case 0x7f:  return "serviceNotSupportedInActiveSession";
    default:    return NULL;
    }
  }
