/*
 *  Copyright 1994-2022 Olivier Girondel
 *
 *  This file is part of lebiniou.
 *
 *  lebiniou is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  lebiniou is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with lebiniou. If not, see <http://www.gnu.org/licenses/>.
 */

#include "bulfius.h"
#include "context.h"
#include "plugins.h"
#include "vui.h"
#include "pthread_utils.h"

// #define DEBUG_WS


static void
websocket_onclose_callback(const struct _u_request *request,
                           struct _websocket_manager *websocket_manager,
                           void *user_data)
{
  Context_t *ctx = (Context_t *)user_data;

#ifdef DEBUG_WS
  VERBOSE(printf("[i] %s (%s): end session for client: %p\n", __FILE__, __func__, websocket_manager));
#endif
  if (!xpthread_mutex_lock(&ctx->ws_clients_mutex)) {
    ctx->ws_clients = g_slist_remove(ctx->ws_clients, (gpointer)websocket_manager);
    xpthread_mutex_unlock(&ctx->ws_clients_mutex);
  }
}


static void
websocket_manager_callback(const struct _u_request *request,
                           struct _websocket_manager *websocket_manager,
                           void *user_data)
{
  Context_t *ctx = (Context_t *)user_data;
  int ret;

#if ULFIUS_VERSION_NUMBER >= 0x020703
  websocket_manager->keep_messages = U_WEBSOCKET_KEEP_NONE;
#endif
  if (!xpthread_mutex_lock(&ctx->ws_clients_mutex)) {
    ctx->ws_clients = g_slist_prepend(ctx->ws_clients, (gpointer)websocket_manager);
    xpthread_mutex_unlock(&ctx->ws_clients_mutex);
  }
#ifdef DEBUG_WS
  VERBOSE(printf("[i] %s (%s): new session, client: %p\n", __FILE__, __func__, websocket_manager));
#endif
  while (ctx->running && (ret = (ulfius_websocket_wait_close(websocket_manager, 2000) == U_WEBSOCKET_STATUS_OPEN))) {
#ifdef DEBUG_WS
    // VERBOSE(printf("[i] %s (%s): websocket_manager_callback: websocket still open: %s\n",
    // __FILE__, __func__, ret ? "true" : "false"));
#endif
  }
}


void
bulfius_send_command_result(Context_t *ctx, const char *cmd_type, const char *cmd, const json_t *arg, const json_t *res, struct _websocket_manager *websocket_manager)
{
  if (NULL == res) {
    json_t *json = json_pack("{ssss}", cmd_type, cmd, "result", "ok");
#ifdef DEBUG_WS
    char *str1 = json_dumps(json, 0);
    char *str2 = json_dumps(arg, 0);
    xdebug("[i] %s (%s) %d: JSON %s result '%s', arg: '%s': %s\n", __FILE__, __func__, __LINE__, cmd_type, cmd, str1, str2);
    xfree(str2);
    xfree(str1);
#endif
    bulfius_websocket_broadcast_json_message(ctx, json, websocket_manager);
    json_decref(json);
  } else {
    json_t *json = json_pack("{ssso}", cmd_type, cmd, "result", res);
#ifdef DEBUG_WS
    char *str1 = json_dumps(arg, 0);
    char *str2 = json_dumps(res, 0);
    xdebug("[i] %s (%s) %d: JSON %s result '%s', arg: '%s': %s\n", __FILE__, __func__, __LINE__, cmd_type, cmd, str1, str2);
    xfree(str2);
    xfree(str1);
#endif
    const int command = str2command(cmd);
    if ((command == UI_CMD_CONNECT) || command == VUI_CONNECT) {
      json_object_set_new(json, "emitter", json_true());
#if ULFIUS_VERSION_NUMBER < 0x020609
      char *str = json_dumps(json, 0);
      ulfius_websocket_send_message(websocket_manager, U_WEBSOCKET_OPCODE_TEXT, strlen(str), str);
#else // ULFIUS_VERSION_NUMBER >= 0x020609
      ulfius_websocket_send_json_message(websocket_manager, json);
#endif
#if ULFIUS_VERSION_NUMBER < 0x020609
      xfree(str);
#endif
#if ULFIUS_VERSION_NUMBER < 0x020703
      ulfius_clear_websocket_message(ulfius_websocket_pop_first_message(websocket_manager->message_list_outcoming));
#endif
    } else {
      bulfius_websocket_broadcast_json_message(ctx, json, websocket_manager);
    }
    json_decref(json);
  }
}


static void
process_legacy_command(Context_t *ctx, const char *cmd, struct _websocket_manager *websocket_manager)
{
  const int command = str2command(cmd);

  if ((command != -1) && is_allowed(command)) {
    Command_t *c = Command_new(CT_SDL2, command, NULL, websocket_manager);
    Context_push_command(ctx, c);
  }
}


static void
process_ui_command(Context_t *ctx, const char *cmd, json_t *arg, struct _websocket_manager *websocket_manager)
{
  const int command = str2command(cmd);

  if ((command != -1) && is_allowed(command)) {
    Command_t *c = Command_new(CT_WEB_UI, command, arg, websocket_manager);
    Context_push_command(ctx, c);
  }
}


static void
process_vui_command(Context_t *ctx, const char *cmd, json_t *arg, struct _websocket_manager *websocket_manager)
{
  const int command = str2command(cmd);

  if ((command != -1) && is_allowed(command)) {
    Command_t *c = Command_new(CT_VUI, command, arg, websocket_manager);
    Context_push_command(ctx, c);
  }
}


static void
process_json_payload(Context_t *ctx, const json_t *payload, struct _websocket_manager *websocket_manager)
{
  assert(NULL != ctx);
  json_t *cmd = json_object_get(payload, "command");

  if (NULL != cmd) {
    if (json_is_string(cmd)) {
      process_legacy_command(ctx, json_string_value(cmd), websocket_manager);
    }
    return;
  }

  cmd = json_object_get(payload, "uiCommand");
  if (NULL != cmd) {
    if (json_is_string(cmd)) {
      process_ui_command(ctx, json_string_value(cmd), json_object_get(payload, "arg"), websocket_manager);
    }
    return;
  }

  cmd = json_object_get(payload, "vuiCommand");
  if (NULL != cmd) {
    if (json_is_string(cmd)) {
      process_vui_command(ctx, json_string_value(cmd), json_object_get(payload, "arg"), websocket_manager);
    }
    return;
  }
}


static void
websocket_incoming_message_callback(const struct _u_request *request,
                                    struct _websocket_manager *websocket_manager,
                                    const struct _websocket_message* last_message,
                                    void *user_data)
{
  Context_t *ctx = (Context_t *)user_data;

  if (last_message->opcode == U_WEBSOCKET_OPCODE_TEXT) {
#ifdef DEBUG_WS
    // VERBOSE(printf("[i] %s (%s): text payload '%.*s'\n", __FILE__, __func__, (int)last_message->data_len, last_message->data));
#endif
    json_t *payload = json_loadb(last_message->data, last_message->data_len, 0, NULL);

    if (NULL != payload) {
      process_json_payload(ctx, payload, websocket_manager);
      json_decref(payload);
    }
  } else if (last_message->opcode == U_WEBSOCKET_OPCODE_BINARY) {
#ifdef DEBUG_WS
    VERBOSE(printf("[i] %s (%s): binary payload\n", __FILE__, __func__));
#endif
  } else if (last_message->opcode == U_WEBSOCKET_OPCODE_CLOSE) {
#ifdef DEBUG_WS
    VERBOSE(printf("[i] %s (%s): got a CLOSE message\n", __FILE__, __func__));
#endif
  } else if (last_message->opcode == U_WEBSOCKET_OPCODE_PING) {
#ifdef DEBUG_WS
    VERBOSE(printf("[i] %s (%s): got a PING message\n", __FILE__, __func__));
#endif
  } else if (last_message->opcode == U_WEBSOCKET_OPCODE_PONG) {
#ifdef DEBUG_WS
    VERBOSE(printf("[i] %s (%s): got a PONG message\n", __FILE__, __func__));
#endif
  }
#if ULFIUS_VERSION_NUMBER < 0x020703
  ulfius_clear_websocket_message(ulfius_websocket_pop_first_message(websocket_manager->message_list_incoming));
#endif
}


int
callback_websocket(const struct _u_request *request, struct _u_response *response, void *user_data)
{
  int ret;

  if ((ret = ulfius_set_websocket_response(response, NULL, NULL,
             &websocket_manager_callback, user_data,
             &websocket_incoming_message_callback, user_data,
             &websocket_onclose_callback, user_data)) == U_OK) {
#if ULFIUS_VERSION_NUMBER >= 0x020700
    ulfius_add_websocket_deflate_extension(response);
#endif
    return U_CALLBACK_CONTINUE;
  } else {
    return U_CALLBACK_ERROR;
  }
}


void
bulfius_websocket_broadcast_json_message(struct Context_s *ctx, json_t *message, const struct _websocket_manager *from)
{
    if (NULL != ctx->ws_clients) {
#if ULFIUS_VERSION_NUMBER < 0x020609
      char *payload = json_dumps(message, 0);
#endif
      json_object_set_new(message, "emitter", json_true());
      if (!xpthread_mutex_lock(&ctx->ws_clients_mutex)) {
        for (GSList *client = ctx->ws_clients; NULL != client; client = client->next) {
          struct _websocket_manager *websocket_manager = (struct _websocket_manager *)client->data;
          json_object_set(message, "emitter", json_boolean(websocket_manager == from));
#if ULFIUS_VERSION_NUMBER >= 0x020609
          ulfius_websocket_send_json_message(websocket_manager, message);
#else
          ulfius_websocket_send_message(websocket_manager, U_WEBSOCKET_OPCODE_TEXT, strlen(payload), payload);
#endif
#if ULFIUS_VERSION_NUMBER < 0x020703
          ulfius_clear_websocket_message(ulfius_websocket_pop_first_message(websocket_manager->message_list_outcoming));
#endif
        }
        xpthread_mutex_unlock(&ctx->ws_clients_mutex);
      }
#if ULFIUS_VERSION_NUMBER < 0x020609
      xfree(payload);
#endif
    }
}


void
bulfius_websocket_send_command_result(Context_t *ctx, const enum Command cmd, json_t *res,
                                      struct _websocket_manager *websocket_manager)
{
  if (NULL == res) {
    json_t *json = json_pack("{ssss}", "command", command2str(cmd), "result", "ok");
#ifdef DEBUG_WS
    VERBOSE(printf("[i] %s (%s) %d: JSON send '%s' command result: 'ok'\n", __FILE__, __func__, __LINE__, command2str(cmd)));
#endif

    bulfius_websocket_broadcast_json_message(ctx, json, websocket_manager);
    json_decref(json);
  } else {
    json_t *json = json_pack("{ssso}", "command", command2str(cmd), "result", res);
#if ULFIUS_VERSION_NUMBER < 0x020609
    char *str = json_dumps(json, JSON_COMPACT);
#endif
#ifdef DEBUG_WS
    char *tmp = json_dumps(res, 0);
    VERBOSE(printf("[i] %s (%s) %d: JSON send '%s' command result: '%s'\n", __FILE__, __func__, __LINE__, command2str(cmd), tmp));
    xfree(tmp);
#endif
    if (cmd == UI_CMD_CONNECT) {
#if ULFIUS_VERSION_NUMBER >= 0x020609
      ulfius_websocket_send_json_message(websocket_manager, json);
#else
      ulfius_websocket_send_message(websocket_manager, U_WEBSOCKET_OPCODE_TEXT, strlen(str), str);
#endif
#if ULFIUS_VERSION_NUMBER < 0x020703
      ulfius_clear_websocket_message(ulfius_websocket_pop_first_message(websocket_manager->message_list_outcoming));
#endif
    } else {
      bulfius_websocket_broadcast_json_message(ctx, json, websocket_manager);
    }
#if ULFIUS_VERSION_NUMBER < 0x020609
    g_free(str);
#endif
    json_decref(json);
  }
}
