/*
;    Project:       Open Vehicle Monitor System
;    Date:          14th March 2017
;
;    Changes:
;    1.0  Initial release
;
;    (C) 2018       Michael Balzer
;
; 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.
*/

// We're using ESP_EARLY_LOG* (direct USB console output) for protocol debug logging.
// To enable protocol debug logging locally, uncomment:
// #define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE

#include "ovms_log.h"
static const char *TAG = "webcommand";

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "buffered_shell.h"
#include "log_buffers.h"
#include "ovms_webserver.h"
#include "ovms_script.h"
#include "ovms_module.h"


struct hcs_writebuf
{
  char* data;
  size_t len;
};


HttpCommandStream::HttpCommandStream(mg_connection* nc, extram::string command,
    bool javascript /*=false*/, int verbosity /*=COMMAND_RESULT_NORMAL*/)
  : OvmsShell(verbosity), MgHandler(nc)
  // Note: due to a gcc bug, the base classes MUST be done in this order,
  //  or compilation will fail with "error: generic thunk code fails for method […] printf".
  // See https://bugzilla.redhat.com/show_bug.cgi?id=1511021
  //  and https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83549
  //  for details.
{
  ESP_EARLY_LOGD(TAG, "HttpCommandStream[%p] init: handler=%p command='%s%s' verbosity=%d", nc, this,
    command.substr(0,200).c_str(), (command.length()>200) ? " [...]" : "", verbosity);

  m_command = command;
  m_javascript = javascript;
  m_done = false;
  m_sent = m_ack = 0;
  Initialize(false);
  SetSecure(true); // Note: assuming user is admin

  // create write queue & command task:
  m_writequeue = xQueueCreate(30, sizeof(hcs_writebuf));
  char name[configMAX_TASK_NAME_LEN];
  snprintf(name, sizeof(name), "%s", command.c_str());
  xTaskCreatePinnedToCore(CommandTask, name,
    CONFIG_OVMS_SYS_COMMAND_STACK_SIZE, (void*)this,
    CONFIG_OVMS_SYS_COMMAND_PRIORITY, &m_cmdtask, CORE(1));
  AddTaskToMap(m_cmdtask);
}

HttpCommandStream::~HttpCommandStream()
{
  hcs_writebuf wbuf;
  if (m_writequeue) {
    while (xQueueReceive(m_writequeue, &wbuf, 0) == pdTRUE)
      free(wbuf.data);
    vQueueDelete(m_writequeue);
  }
}


void HttpCommandStream::Initialize(bool print)
{
  if (!m_javascript) {
    OvmsShell::Initialize(print);
    ProcessChar('\n');
  }
}


void HttpCommandStream::CommandTask(void* object)
{
  HttpCommandStream* me = (HttpCommandStream*) object;

  ESP_LOGI(TAG, "HttpCommandStream[%p]: %d bytes free, executing: %s%s",
    me->m_nc, heap_caps_get_free_size(MALLOC_CAP_8BIT),
    me->m_command.substr(0,200).c_str(), (me->m_command.length()>200) ? " [...]" : "");

  // execute command:
  if (me->m_javascript) {
    #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
      MyDuktape.DuktapeEvalNoResult(me->m_command.c_str(), me);
    #else
      me->puts("ERROR: Javascript support disabled");
    #endif
  } else {
    me->ProcessChars(me->m_command.data(), me->m_command.size());
    me->ProcessChar('\n');
  }

  me->m_done = true;

#if MG_ENABLE_BROADCAST && WEBSRV_USE_MG_BROADCAST
  if (m_writequeue && uxQueueMessagesWaiting(me->m_writequeue) > 0) {
    ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] RequestPollLast, qlen=%d done=%d sent=%d ack=%d", me->m_nc, uxQueueMessagesWaiting(me->m_writequeue), me->m_done, me->m_sent, me->m_ack);
    me->RequestPoll();
    ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] RequestPollDone, qlen=%d done=%d sent=%d ack=%d", me->m_nc, uxQueueMessagesWaiting(me->m_writequeue), me->m_done, me->m_sent, me->m_ack);
  }
#endif // MG_ENABLE_BROADCAST && WEBSRV_USE_MG_BROADCAST

  while (me->m_nc)
    vTaskDelay(10/portTICK_PERIOD_MS);
  delete me;
  vTaskDelete(NULL);
}


void HttpCommandStream::ProcessQueue()
{
  size_t txlen = 0;
  hcs_writebuf wbuf;

  if (m_writequeue) {
    while (txlen < XFER_CHUNK_SIZE && xQueueReceive(m_writequeue, &wbuf, 0) == pdTRUE) {
      if (m_nc) {
        mg_send_http_chunk(m_nc, wbuf.data, wbuf.len);
        txlen += wbuf.len;
      }
      free(wbuf.data);
    }

    if (txlen) {
      m_sent += txlen;
      ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] ProcessQueue txlen=%d, qlen=%d done=%d sent=%d ack=%d",
        m_nc, txlen, uxQueueMessagesWaiting(m_writequeue), m_done, m_sent, m_ack);
    }
  }

  if (m_done && m_sent == m_ack) {
    ESP_EARLY_LOGD(TAG, "HttpCommandStream[%p] DONE, %d bytes sent, %d bytes free",
      m_nc, m_sent, heap_caps_get_free_size(MALLOC_CAP_8BIT));
    if (m_nc) {
      m_nc->flags |= MG_F_SEND_AND_CLOSE; // necessary to prevent mg_broadcast lockups
      mg_send_http_chunk(m_nc, "", 0);
      m_nc->user_data = NULL;
      m_nc = NULL;
    }
  }
}


int HttpCommandStream::HandleEvent(int ev, void* p)
{
  switch (ev)
  {
    case MG_EV_POLL:
      // check for new transmission:
      ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] EV_POLL qlen=%d done=%d sent=%d ack=%d",
        m_nc, m_writequeue ? uxQueueMessagesWaiting(m_writequeue) : -1, m_done, m_sent, m_ack);
      if (m_ack == m_sent)
        ProcessQueue();
      break;

    case MG_EV_SEND:
      // last transmission has finished:
      ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] EV_SEND qlen=%d done=%d sent=%d ack=%d",
        m_nc, m_writequeue ? uxQueueMessagesWaiting(m_writequeue) : -1, m_done, m_sent, m_ack);
      m_ack = m_sent;
      ProcessQueue();
      break;

    case MG_EV_CLOSE:
      ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] EV_CLOSE qlen=%d done=%d sent=%d ack=%d",
        m_nc, m_writequeue ? uxQueueMessagesWaiting(m_writequeue) : -1, m_done, m_sent, m_ack);
      // connection has been closed, possibly externally:
      // we need to let the command task finish normally to prevent problems
      // due to lost/locked ressources, so we just detach:
      m_nc->user_data = NULL;
      m_nc = NULL;
      ProcessQueue();   // empty queue (no tx) to prevent task lockup on write
      ev = 0;           // prevent deletion by main event handler
      break;

    default:
      break;
  }

  return ev;
}


int HttpCommandStream::puts(const char* s)
{
  if (!m_nc)
    return 0;
  write(s, strlen(s));
  write("\n", 1);
  return 0;
}

int HttpCommandStream::printf(const char* fmt, ...)
{
  if (!m_nc)
    return 0;
  char *buffer = NULL;
  va_list args;
  va_start(args, fmt);
  int ret = vasprintf(&buffer, fmt, args);
  va_end(args);
  if (ret >= 0) {
    write(buffer, ret);
    free(buffer);
  }
  return ret;
}

ssize_t HttpCommandStream::write(const void *buf, size_t nbyte)
{
  if (!m_nc || nbyte == 0)
    return 0;

  if (!m_writequeue)
    return nbyte;

  hcs_writebuf wbuf;
  wbuf.data = (char*) ExternalRamMalloc(nbyte);
  wbuf.len = nbyte;
  memcpy(wbuf.data, buf, nbyte);

  if (xQueueSend(m_writequeue, &wbuf, portMAX_DELAY) != pdTRUE) {
    free(wbuf.data);
    return nbyte;
  }

#if MG_ENABLE_BROADCAST && WEBSRV_USE_MG_BROADCAST
  if (uxQueueMessagesWaiting(m_writequeue) == 1) {
    ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] RequestPoll, qlen=1 done=%d sent=%d ack=%d", m_nc, m_done, m_sent, m_ack);
    RequestPoll();
    ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] RequestPollDone, qlen=%d done=%d sent=%d ack=%d", m_nc, uxQueueMessagesWaiting(m_writequeue), m_done, m_sent, m_ack);
  }
  else
#endif // MG_ENABLE_BROADCAST && WEBSRV_USE_MG_BROADCAST
    ESP_EARLY_LOGV(TAG, "HttpCommandStream[%p] AddQueue, qlen=%d done=%d sent=%d ack=%d", m_nc, uxQueueMessagesWaiting(m_writequeue), m_done, m_sent, m_ack);

  return nbyte;
}

void HttpCommandStream::Log(LogBuffers* message)
{
  // writing could block, logging is done via the websocket stream
  message->release();
}
