#include "PsychicWebSocket.h"

/*************************************/
/*  PsychicWebSocketRequest      */
/*************************************/

PsychicWebSocketRequest::PsychicWebSocketRequest(PsychicRequest* req) : PsychicRequest(req->server(), req->request()),
                                                                        _client(req->client())
{
}

PsychicWebSocketRequest::~PsychicWebSocketRequest()
{
}

PsychicWebSocketClient* PsychicWebSocketRequest::client()
{
  return &_client;
}

esp_err_t PsychicWebSocketRequest::reply(httpd_ws_frame_t* ws_pkt)
{
  return httpd_ws_send_frame(this->_req, ws_pkt);
}

esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void* data, size_t len)
{
  httpd_ws_frame_t ws_pkt;
  memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));

  ws_pkt.payload = (uint8_t*)data;
  ws_pkt.len = len;
  ws_pkt.type = op;

  return this->reply(&ws_pkt);
}

esp_err_t PsychicWebSocketRequest::reply(const char* buf)
{
  return this->reply(HTTPD_WS_TYPE_TEXT, buf, strlen(buf));
}

/*************************************/
/*  PsychicWebSocketClient   */
/*************************************/

PsychicWebSocketClient::PsychicWebSocketClient(PsychicClient* client)
    : PsychicClient(client->server(), client->socket())
{
}

PsychicWebSocketClient::~PsychicWebSocketClient()
{
}

void PsychicWebSocketClient::_sendMessageCallback(esp_err_t err, int socket, void* arg)
{
  // free our frame.
  httpd_ws_frame_t* ws_pkt = (httpd_ws_frame_t*)arg;
  free(ws_pkt->payload);
  free(ws_pkt);

  if (err == ESP_OK)
    return;
  else if (err == ESP_FAIL)
    ESP_LOGE(PH_TAG, "Websocket: send - socket error (#%d)", socket);
  else if (err == ESP_ERR_INVALID_STATE)
    ESP_LOGE(PH_TAG, "Websocket: Handshake was already done beforehand (#%d)", socket);
  else if (err == ESP_ERR_INVALID_ARG)
    ESP_LOGE(PH_TAG, "Websocket: Argument is invalid (null or non-WebSocket) (#%d)", socket);
  else
    ESP_LOGE(PH_TAG, "Websocket: Send message unknown error. (#%d)", socket);
}

esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_frame_t* ws_pkt)
{
  return sendMessage(ws_pkt->type, ws_pkt->payload, ws_pkt->len);
}

esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_type_t op, const void* data, size_t len)
{
  // init our frame.
  httpd_ws_frame_t* ws_pkt = (httpd_ws_frame_t*)malloc(sizeof(httpd_ws_frame_t));
  if (ws_pkt == NULL) {
    ESP_LOGE(PH_TAG, "Websocket: out of memory");
    return ESP_ERR_NO_MEM;
  }
  memset(ws_pkt, 0, sizeof(httpd_ws_frame_t)); // zero the datastructure out

  // allocate for event text
  ws_pkt->payload = (uint8_t*)malloc(len);
  if (ws_pkt->payload == NULL) {
    ESP_LOGE(PH_TAG, "Websocket: out of memory");
    free(ws_pkt); // free our other memory
    return ESP_ERR_NO_MEM;
  }
  memcpy(ws_pkt->payload, data, len);

  ws_pkt->len = len;
  ws_pkt->type = op;

  esp_err_t err = httpd_ws_send_data_async(server(), socket(), ws_pkt, PsychicWebSocketClient::_sendMessageCallback, ws_pkt);

  // take care of memory
  if (err != ESP_OK) {
    free(ws_pkt->payload);
    free(ws_pkt);
  }

  return err;
}

esp_err_t PsychicWebSocketClient::sendMessage(const char* buf)
{
  return this->sendMessage(HTTPD_WS_TYPE_TEXT, buf, strlen(buf));
}

PsychicWebSocketHandler::PsychicWebSocketHandler() : PsychicHandler(),
                                                     _onOpen(NULL),
                                                     _onFrame(NULL),
                                                     _onClose(NULL)
{
}

PsychicWebSocketHandler::~PsychicWebSocketHandler()
{
}

PsychicWebSocketClient* PsychicWebSocketHandler::getClient(int socket)
{
  PsychicClient* client = PsychicHandler::getClient(socket);
  if (client == NULL)
    return NULL;

  if (client->_friend == NULL) {
    return NULL;
  }

  return (PsychicWebSocketClient*)client->_friend;
}

PsychicWebSocketClient* PsychicWebSocketHandler::getClient(PsychicClient* client)
{
  return getClient(client->socket());
}

void PsychicWebSocketHandler::addClient(PsychicClient* client)
{
  client->_friend = new PsychicWebSocketClient(client);
  PsychicHandler::addClient(client);
}

void PsychicWebSocketHandler::removeClient(PsychicClient* client)
{
  PsychicHandler::removeClient(client);
  delete (PsychicWebSocketClient*)client->_friend;
  client->_friend = NULL;
}

void PsychicWebSocketHandler::openCallback(PsychicClient* client)
{
  PsychicWebSocketClient* buddy = getClient(client);
  if (buddy == NULL) {
    return;
  }

  if (_onOpen != NULL)
    _onOpen(getClient(buddy));
}

void PsychicWebSocketHandler::closeCallback(PsychicClient* client)
{
  PsychicWebSocketClient* buddy = getClient(client);
  if (buddy == NULL) {
    return;
  }

  if (_onClose != NULL)
    _onClose(getClient(buddy));
}

bool PsychicWebSocketHandler::isWebSocket() { return true; }

esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest* request, PsychicResponse* response)
{
  // lookup our client
  PsychicClient* client = checkForNewClient(request->client());

  // beginning of the ws URI handler and our onConnect hook
  if (request->method() == HTTP_GET) {
    if (client->isNew)
      openCallback(client);

    return ESP_OK;
  }

  // prep our request
  PsychicWebSocketRequest wsRequest(request);

  // init our memory for storing the packet
  httpd_ws_frame_t ws_pkt;
  memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
  ws_pkt.type = HTTPD_WS_TYPE_TEXT;
  uint8_t* buf = NULL;

  /* Set max_len = 0 to get the frame len */
  esp_err_t ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, 0);
  if (ret != ESP_OK) {
    ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed to get frame len with %s", esp_err_to_name(ret));
    return ret;
  }

  // okay, now try to load the packet
  // ESP_LOGD(PH_TAG, "frame len is %d", ws_pkt.len);
  if (ws_pkt.len) {
    /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
    buf = (uint8_t*)calloc(1, ws_pkt.len + 1);
    if (buf == NULL) {
      ESP_LOGE(PH_TAG, "Failed to calloc memory for buf");
      return ESP_ERR_NO_MEM;
    }
    ws_pkt.payload = buf;
    /* Set max_len = ws_pkt.len to get the frame payload */
    ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, ws_pkt.len);
    if (ret != ESP_OK) {
      ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed with %s", esp_err_to_name(ret));
      free(buf);
      return ret;
    }
    // ESP_LOGD(PH_TAG, "Got packet with message: %s", ws_pkt.payload);
  }
  if (ws_pkt.type == HTTPD_WS_TYPE_PING) {
      // Respond to ping with pong using the same payload
      ret = wsRequest.reply(HTTPD_WS_TYPE_PONG, ws_pkt.payload, ws_pkt.len);
      if (ret != ESP_OK) {
          ESP_LOGE(PH_TAG, "Failed to send pong response: %s", esp_err_to_name(ret));
      }
  }
  // Text messages are our payload.
  if (ws_pkt.type == HTTPD_WS_TYPE_TEXT || ws_pkt.type == HTTPD_WS_TYPE_BINARY) {
    if (this->_onFrame != NULL)
      ret = this->_onFrame(&wsRequest, &ws_pkt);
  }

  // logging housekeeping
  if (ret != ESP_OK)
    ESP_LOGE(PH_TAG, "httpd_ws_send_frame failed with %s", esp_err_to_name(ret));
  // ESP_LOGD(PH_TAG, "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d",
  //   request->server(),
  //   httpd_req_to_sockfd(request->request()),
  //   httpd_ws_get_fd_info(request->server()->server, httpd_req_to_sockfd(request->request())));

  // dont forget to release our buffer memory
  free(buf);

  return ret;
}

PsychicWebSocketHandler* PsychicWebSocketHandler::onOpen(PsychicWebSocketClientCallback fn)
{
  _onOpen = fn;
  return this;
}

PsychicWebSocketHandler* PsychicWebSocketHandler::onFrame(PsychicWebSocketFrameCallback fn)
{
  _onFrame = fn;
  return this;
}

PsychicWebSocketHandler* PsychicWebSocketHandler::onClose(PsychicWebSocketClientCallback fn)
{
  _onClose = fn;
  return this;
}

void PsychicWebSocketHandler::sendAll(httpd_ws_frame_t* ws_pkt)
{
  for (PsychicClient* client : _clients) {
    // ESP_LOGD(PH_TAG, "Active client (fd=%d) -> sending async message", client->socket());

    if (client->_friend == NULL) {
      continue;
    }

    if (((PsychicWebSocketClient*)client->_friend)->sendMessage(ws_pkt) != ESP_OK)
      break;
  }
}

void PsychicWebSocketHandler::sendAll(httpd_ws_type_t op, const void* data, size_t len)
{
  httpd_ws_frame_t ws_pkt;
  memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));

  ws_pkt.payload = (uint8_t*)data;
  ws_pkt.len = len;
  ws_pkt.type = op;

  this->sendAll(&ws_pkt);
}

void PsychicWebSocketHandler::sendAll(const char* buf)
{
  this->sendAll(HTTPD_WS_TYPE_TEXT, buf, strlen(buf));
}
