/*
 *  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/>.
 */

#ifdef MAGICKWAND7
#include <MagickWand/MagickWand.h>
#else
#include <wand/magick_wand.h>
#endif
#include "bulfius.h"
#include "context.h"


// #define DEBUG_PREVIEW


static void
websocket_onclose_callback(const struct _u_request *request,
                           struct _websocket_manager *websocket_manager,
                           void *user_data)
{
#ifdef DEBUG_PREVIEW
  xdebug("%s (%s): end session for client: %p\n", __FILE__, __func__, websocket_manager);
#endif
}


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;

  assert(NULL != ctx);
#ifdef DEBUG_PREVIEW
  xdebug("%s (%s): new client: %p\n", __FILE__, __func__, websocket_manager);
#endif
#if ULFIUS_VERSION_NUMBER >= 0x020703
  websocket_manager->keep_messages = U_WEBSOCKET_KEEP_NONE;
#endif
  while (ctx->running && (ret = (ulfius_websocket_wait_close(websocket_manager, 2000) == U_WEBSOCKET_STATUS_OPEN))) {
#ifdef DEBUG_PREVIEW
    // xdebug("%s (%s): websocket_manager_callback: websocket still open: %s\n",
    // __FILE__, __func__, ret ? "true" : "false");
#endif
  }
}


static uint8_t *
get_frame_rescaled(Context_t *ctx, const uint16_t width, const uint16_t height, size_t *png_datalen)
{
  // maximum rescale to qHD
  if ((width > 0) && (height > 0) && (width <= 960) && (height <= 540)) {
    uint8_t *png = NULL;

    Context_to_PNG(ctx, &png, png_datalen, width, height);

    return png;
  } else {
    return NULL;
  }
}


static uint8_t *
get_frame(Context_t *ctx, size_t *png_datalen)
{
  uint8_t *png = NULL;

  Context_to_PNG(ctx, &png, png_datalen, 0, 0);

  return png;
}


// #define NO_FRAME
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, "getFrame");
  if (NULL != cmd) {
    json_t *width_j = json_object_get(cmd, "width");
    json_t *height_j = json_object_get(cmd, "height");
    int ret;

    // DEBUG_JSON("payload", payload, 0);
    if (json_is_number(width_j) && json_is_number(height_j)) {
      size_t png_datalen;
      uint8_t *png = get_frame_rescaled(ctx, json_number_value(width_j), json_number_value(height_j), &png_datalen);

      if (png_datalen) {
#ifdef NO_FRAME
        if (drand48() >= 0.04) {
          if ((ret = ulfius_websocket_send_message(websocket_manager, U_WEBSOCKET_OPCODE_BINARY, png_datalen, (char *)png)) != U_OK) {
            fprintf(stderr, "%s:%s (rescaled PNG) ulfius_websocket_send_message() failed: size= %zu ret= %d\n", __FILE__, __func__, png_datalen, ret);
          }
#if ULFIUS_VERSION_NUMBER < 0x020703
          ulfius_clear_websocket_message(ulfius_websocket_pop_first_message(websocket_manager->message_list_outcoming));
#endif
#ifdef DEBUG_PREVIEW
          xdebug("%s (%s): frame sent\n", __FILE__, __func__);
#endif
        } else {
          xdebug("%s: not sending frame\n", __func__);
        }
#else
        if ((ret = ulfius_websocket_send_message(websocket_manager, U_WEBSOCKET_OPCODE_BINARY, png_datalen, (char *)png)) != U_OK) {
          fprintf(stderr, "%s:%s (rescaled PNG) ulfius_websocket_send_message() failed: size= %zu ret= %d\n", __FILE__, __func__, png_datalen, ret);
        }
#if ULFIUS_VERSION_NUMBER < 0x020703
        ulfius_clear_websocket_message(ulfius_websocket_pop_first_message(websocket_manager->message_list_outcoming));
#endif
#endif
        MagickRelinquishMemory(png);
      } else { // this is not supposed to happen
        assert(NULL == png);
        fprintf(stderr, "[!] %s: png_datalen= %zu\n", __func__, png_datalen);
      }
    } else {
      size_t png_datalen;
      uint8_t *png = get_frame(ctx, &png_datalen);

      if (png_datalen) {
#ifdef NO_FRAME
        if (drand48() >= 0.04) {
          if ((ret = ulfius_websocket_send_message(websocket_manager, U_WEBSOCKET_OPCODE_BINARY, png_datalen, (char *)png)) != U_OK) {
            fprintf(stderr, "%s:%s ulfius_websocket_send_message() failed: size= %zu ret= %d\n", __FILE__, __func__, png_datalen, ret);
          }
#if ULFIUS_VERSION_NUMBER < 0x020703
          ulfius_clear_websocket_message(ulfius_websocket_pop_first_message(websocket_manager->message_list_outcoming));
#endif
#ifdef DEBUG_PREVIEW
          xdebug("%s (%s): frame sent\n", __FILE__, __func__);
#endif
        } else {
          xdebug("%s: not sending frame\n", __func__);
        }
#else
        if ((ret = ulfius_websocket_send_message(websocket_manager, U_WEBSOCKET_OPCODE_BINARY, png_datalen, (char *)png)) != U_OK) {
          fprintf(stderr, "%s:%s ulfius_websocket_send_message() failed: size= %zu ret= %d\n", __FILE__, __func__, png_datalen, ret);
        }
#if ULFIUS_VERSION_NUMBER < 0x020703
        ulfius_clear_websocket_message(ulfius_websocket_pop_first_message(websocket_manager->message_list_outcoming));
#endif
#endif
        MagickRelinquishMemory(png);
      } else { // this is not supposed to happen
        assert(NULL == png);
        fprintf(stderr, "[!] %s: png_datalen= %zu\n", __func__, png_datalen);
      }
    }
  }
}


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_PREVIEW
    xdebug("%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) {
      if (NULL != json_object_get(payload, "getFrame")) {
        process_json_payload(ctx, payload, websocket_manager);
      }
      json_decref(payload);
    }
  } else if (last_message->opcode == U_WEBSOCKET_OPCODE_BINARY) {
#ifdef DEBUG_PREVIEW
    xdebug("%s (%s): binary payload\n", __FILE__, __func__);
#endif
  } else if (last_message->opcode == U_WEBSOCKET_OPCODE_CLOSE) {
#ifdef DEBUG_PREVIEW
    xdebug("%s (%s): got a CLOSE message\n", __FILE__, __func__);
#endif
  } else if (last_message->opcode == U_WEBSOCKET_OPCODE_PING) {
#ifdef DEBUG_PREVIEW
    xdebug("%s (%s): got a PING message\n", __FILE__, __func__);
#endif
  } else if (last_message->opcode == U_WEBSOCKET_OPCODE_PONG) {
#ifdef DEBUG_PREVIEW
    xdebug("%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_preview_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;
  }
}
