
// SPDX-License-Identifier: CC-BY-NC-SA-4.0
//
// Copyright (C) 2026 Bit by Bit Signal Processing LLC (https://bxbsp.com)
//
// This work is placed under the "Creative Commons Attribution
// NonCommercial ShareAlike 4.0 International" license, known
// by the shortened acronym "CC-BY-NC-SA-4.0".
//
// This work 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.
//
// A CC-BY-NC-SA-4.0 license allows you to use this work for
// noncommercial purposes so long as attribution is made to the
// original author.  Modified versions of this work may be distributed,
// but only under the same license.  For further details, see the
// Creative Commons License "CC-BY-NC-SA-4.0".
//
// You should have received a copy of the CC-BY-NC-SA-4.0 license
// along with this work. If not, see
// <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
//
//

#ifndef WEB_INTERFACE_HH
#define WEB_INTERFACE_HH

#include <libwebsockets.h>
#include <string.h>
#include <signal.h>

#include "protocol_http.hh"
#include "runnable.hh"
#include "synchronized_data.hh"
#include "List.hh"

// Text flags, identical to those in draw_text.hh
#ifndef DRAW_TEXT_HH
#define DRAW_TEXT_X_LEFT     0x00
#define DRAW_TEXT_X_CENTER   0x01
#define DRAW_TEXT_X_RIGHT    0x02
#define DRAW_TEXT_X_MASK     0x03

#define DRAW_TEXT_Y_BOTTOM   0x00
#define DRAW_TEXT_Y_CENTER   0x10
#define DRAW_TEXT_Y_TOP      0x20
#define DRAW_TEXT_Y_MASK     0x30

#define DRAW_TEXT_ROTATE_0        0x000
#define DRAW_TEXT_ROTATE_90_LEFT  0x100
#define DRAW_TEXT_ROTATE_90_RIGHT 0x200
#define DRAW_TEXT_ROTATE_MASK     0x300

#define MAX_COLORS 22
#endif

#define BEEP_ID_TOUCH_RECOGNIZED 1
#define BEEP_ID_ENTRY_ACCEPTED   2
#define BEEP_ID_ENTRY_REJECTED   3


//
// Commands server to GUI client
//
#define WS_COMMAND_RECTANGLE                   101
#define WS_COMMAND_TRIANGLE                    102
#define WS_COMMAND_CIRCLE                      103
#define WS_COMMAND_LINE                        104
#define WS_COMMAND_GRAPH_DATA                  105
#define WS_COMMAND_TEXT                        107
#define WS_COMMAND_FONT                        108
#define WS_COMMAND_MULTICOLORED_TEXT           109
#define WS_COMMAND_SVG                         110

#define WS_COMMAND_SET_CURRENT_LAYER           201
#define WS_COMMAND_CLEAR_LAYER                 202
#define WS_COMMAND_MAKE_LAYER_VISIBLE          203
#define WS_COMMAND_MAKE_LAYER_HIDDEN           204
#define WS_COMMAND_SET_LAYER_VISIBILITY        205
#define WS_COMMAND_COPY_LAYER_TO_LAYER         206
#define WS_COMMAND_MOVE_LAYER_TO_LAYER         207

#define WS_COMMAND_BEEP                        301

#define WS_COMMAND_QUEUE_NUM_ITEMS              10
#define WS_COMMAND_QUEUE_MAX_LENGTH         500000

//
// Commands server to GUI client with response
//
#define WS_COMMAND_TEXT_SIZE                   401

//
// Responses GUI client to server
//
#define WS_EVENT_BUTTON_DOWN                  1101
#define WS_EVENT_BUTTON_UP                    1102
#define WS_EVENT_MOUSE_MOVE                   1103
#define WS_EVENT_WHEEL                        1104
#define WS_EVENT_TOUCHSTART                   1105
#define WS_EVENT_TOUCHEND                     1106
#define WS_EVENT_TOUCHMOVE                    1107
#define WS_EVENT_TOUCHCANCEL                  1108

#define WS_EVENT_RESIZE                       1201

#define WS_EVENT_DEBUG_TEXT                   1301

#define WS_EVENT_TEXT_SIZE                    1401

#define WS_EVENT_GRAPH_COUNT_UPDATE           1501

#define WS_EVENT_QUEUE_NUM_ITEMS               128
#define WS_EVENT_QUEUE_MAX_LENGTH             1000


#define WS_MAX_LAYERS                           32

//
// Basic packet structure, for commands (transmit), with built-in LWS_PRE
//
struct ws_command_packet
{
  uint8_t   lws_scratch_buffer[LWS_PRE];
  uint32_t  type;
  uint32_t  length;

  uint8_t* packet_start()  { return (uint8_t*) &(this->type); }
  uint32_t packet_length() { return length; }

  //ws_command_packet() { memset(this, 0, sizeof(*this)); }
};


//
// Specific command types, extending the ws_command_packet
//
struct ws_command_rectangle : ws_command_packet
{
  uint32_t color;
  float    x1;
  float    y1;
  float    x2;
  float    y2;
};

struct ws_command_triangle : ws_command_packet
{
  uint32_t color;
  float    x1;
  float    y1;
  float    x2;
  float    y2;
  float    x3;
  float    y3;
};

struct ws_command_circle : ws_command_packet
{
  uint32_t color;
  float    center_x;
  float    center_y;
  float    radius;
};

struct ws_command_line : ws_command_packet
{
  uint32_t color;
  float    width;
  float    x1;
  float    y1;
  float    x2;
  float    y2;
};

#ifndef DATA_POINT
#define DATA_POINT
struct data_point
{
  float min_y;
  float max_y;
};
#endif

struct ws_command_graph_data : ws_command_packet
{
  uint32_t     color;
  uint32_t     num_points;
  float        x_start;
  float        x_step;
  float        x_at_left;
  float        x_at_right;
  float        y_at_bottom;
  float        y_at_top;
  float        x_offset;
  float        y_offset;
  float        width;
  float        height;
  uint32_t     graph_count;
  data_point   y_data[0]; // First point of data.  Rest follows.
};


struct ws_command_text : ws_command_packet
{
  uint32_t  color;
  uint32_t  flags;
  float     x;
  float     y;
  uint32_t  text_length;
  uint8_t   text[0];  // The first bytes of the text.  Rest follows.  Total size is sizeof(actual_text) + sizeof(ws_command_data_text).
};

struct ws_command_multicolored_text : ws_command_packet
{
  uint32_t  colors[MAX_COLORS];
  uint32_t  flags;
  float     x;
  float     y;
  uint32_t  text_length;
  uint8_t   text[0];  // The first bytes of the text.  Rest follows.  Total size is sizeof(actual_text) + sizeof(ws_command_data_text).
};

struct ws_command_font : ws_command_packet
{
  uint32_t  font_size;
  uint32_t  font_name_length;
  uint8_t   font_name[0];  // The first bytes of the text font.  Rest follows.  Total size is sizeof(actual_text) + sizeof(ws_command_data_text).
};


//
// If width is zero, autosizes width to keep aspect and match height.  If height is zero, autosizes height to keep aspect and match
// width.  If both are zero, keeps original deisgned size.  If both are nonzero, keeps original aspect ratio and makes as big
// as possible to fit in the specified size.
//
struct ws_command_svg : ws_command_packet
{
  float     x;
  float     y;
  float     width;
  float     height;
  uint32_t  text_length;
  uint8_t   text[0];
};


struct ws_command_set_current_layer : ws_command_packet
{
  uint32_t layer;
};

struct ws_command_clear_layer : ws_command_packet
{
  uint32_t layer;
};

struct ws_command_make_layer_visible : ws_command_packet
{
  uint32_t layer;
};

struct ws_command_make_layer_hidden : ws_command_packet
{
  uint32_t layer;
};


struct ws_command_set_layer_visibility : ws_command_packet
{
  uint32_t layer_mask;
};

struct ws_command_copy_layer_to_layer : ws_command_packet
{
  uint32_t layer_from;
  uint32_t layer_to;
};

struct ws_command_move_layer_to_layer : ws_command_packet
{
  uint32_t layer_from;
  uint32_t layer_to;
};

struct ws_command_beep : ws_command_packet
{
  uint32_t beep_id;
};

struct ws_command_text_size : ws_command_packet
{
  uint32_t  text_length;
  uint8_t   text[0];  // The first bytes of the text.  Rest follows.  Total size is sizeof(actual_text) + sizeof(ws_command_data_text).
};


//
// Basic packet structure, for events (receive)
//
struct ws_event_packet
{
  uint32_t  type;
  uint32_t  length;
};

struct ws_event_one_touch
{
  int32_t x;
  int32_t y;
};

#define WS_EVENT_BUTTON_LEFT    0
#define WS_EVENT_BUTTON_CENTER  1
#define WS_EVENT_BUTTON_RIGHT   2

struct ws_event_button_down : ws_event_packet
{
  int32_t  x;
  int32_t  y;
  uint32_t button;
};

struct ws_event_button_up : ws_event_packet
{
  int32_t  x;
  int32_t  y;
  uint32_t button;
};

struct ws_event_mouse_move : ws_event_packet
{
  int32_t  x;
  int32_t  y;
};

struct ws_event_wheel : ws_event_packet
{
  int32_t  x;
  int32_t  y;
  int32_t  steps;  // positive = away, negative = towards?
};


struct ws_event_touch : ws_event_packet
{
  uint32_t            num_touches;
  ws_event_one_touch  touches[1];  // The first touch.  Rest follow.
};

struct ws_event_resize : ws_event_packet
{
  int32_t  width;
  int32_t  height;
};

struct ws_event_debug_text : ws_event_packet
{
  uint8_t  text[1];  // The first bytes of the text.  Rest follows.  Total size is sizeof(actual_text) + sizeof(ws_command_data_text).
};

struct ws_event_text_size : ws_event_packet
{
  float  width;
  float  height;
};

struct ws_event_graph_count_update : ws_event_packet
{
  uint32_t graph_count;
};



struct text_size_data
{
  int width;
  int height;
};


class web_socket_connection
{
public:
  
  int ID;  // First connection is 0, then count up.  This can be used to keep track of the connections
  
  synchronized_data<text_size_data> ts;

  // Become connected once we get width/height information.  Leave connected state when
  // a delete is pending.  Only allow deletion when allow_deletion flag is true.
  volatile bool connected;
  volatile bool need_to_disconnect;
  
  uint32_t          last_graph_count_sent;
  volatile uint32_t last_graph_count_received;

  //
  // Take us out of the connected state, so no more text size waits can be initiated.  Cancel any text size
  // waits that are pending.
  //
  void flag_delete_pending() { connected = false; usleep(100); ts.lock(); ts.unlock(); }
  
  
  struct lws* wsi;  // One for each session, but we only handle a single session currently.
  
  volatile int width;
  volatile int height;
  
  //
  // Outgoing command queue, rotating buffer
  //
  int      command_queue_in_position;
  int      command_queue_out_position;
  uint8_t  command_queue[WS_COMMAND_QUEUE_NUM_ITEMS][WS_COMMAND_QUEUE_MAX_LENGTH];
  
  //
  // Incoming event queue, rotating buffer
  //
  int      event_queue_in_position;
  int      event_queue_out_position;
  uint8_t  event_queue[WS_EVENT_QUEUE_NUM_ITEMS][WS_EVENT_QUEUE_MAX_LENGTH];

  //
  // Next position is -1 if queue is currently full.  Wait version will never return -1; if the queue
  // is full it will wait to return until it's got room.
  //
  int next_input_position_for_command_queue();
  int next_input_position_for_command_queue_wait();
  int next_input_position_for_event_queue();
  int next_input_position_for_event_queue_wait();

  int  num_commands_on_command_queue();
  bool command_is_on_command_queue();
  bool event_is_on_event_queue();

  bool ready_for_more_graph_data();
  //
  // Functions to make command packets and put them on the outgoing queue.  Callable from the main thread only.
  //
  void ws_command_rectangle                  ( uint32_t     color,
			                       float        x1,
			                       float        y1,
			                       float        x2,
			                       float        y2);

  void ws_command_triangle                   ( uint32_t     color,
			                       float        x1,
			                       float        y1,
			                       float        x2,
			                       float        y2,
			                       float        x3,
			                       float        y3);

  void ws_command_circle                     ( uint32_t     color,
			                       float        center_x,
			                       float        center_y,
			                       float        radius);

  void ws_command_line                       ( uint32_t     color,
					       float        width,
			                       float        x1,
			                       float        y1,
			                       float        x2,
			                       float        y2);

  void ws_command_graph_data                 ( uint32_t     color,
			                       int          num_points,
			                       float        x_start,
			                       float        x_step,
					       float        x_at_left,
					       float        x_at_right,
					       float        y_at_bottom,
					       float        y_at_top,
					       float        x_offset,
					       float        y_offset,
					       float        width,
					       float        height,
			                       data_point*  y_data);

  void ws_command_text                       ( uint32_t     color,
					       uint32_t     flags,
			                       float        x,
			                       float        y,
			                       const char*  text);

  void ws_command_text                       ( uint32_t        color,
					       uint32_t        flags,
			                       float           x,
			                       float           y,
			                       const wchar_t*  text);

  void ws_command_text                       ( uint32_t         color,
					       uint32_t         flags,
			                       float            x,
			                       float            y,
			                       const char16_t*  text);

  void ws_command_text                       ( uint32_t         color,
					       uint32_t         flags,
			                       float            x,
			                       float            y,
			                       const char32_t*  text);

  //
  // Up to 10 different colors, set by codes in the text to switch color.
  // ASCII x = 10 (0x0A) and up to indicate a switch to color color[x-10].
  //
  void ws_command_multicolored_text          ( uint32_t     colors[MAX_COLORS],
					       int          start_color,
					       uint32_t     flags,
			                       float        x,
			                       float        y,
			                       const char*  text);

  void ws_command_multicolored_text          ( uint32_t        colors[MAX_COLORS],
					       int             start_color,
					       uint32_t        flags,
			                       float           x,
			                       float           y,
			                       const wchar_t*  text);

  void ws_command_multicolored_text          ( uint32_t         colors[MAX_COLORS],
					       int              start_color,
					       uint32_t         flags,
			                       float            x,
			                       float            y,
			                       const char16_t*  text);
  
  void ws_command_multicolored_text          ( uint32_t         colors[MAX_COLORS],
					       int              start_color,
					       uint32_t         flags,
			                       float            x,
			                       float            y,
			                       const char32_t*  text);

  void ws_command_font                       ( const char*  font_name,
			                       int          size_pts);

  void ws_command_svg                        ( const char*  svg_data,
					       float        offset_x=0,
					       float        offset_y=0,
					       float        draw_width=0,
					       float        draw_height=0);
  
  void ws_command_set_current_layer          ( uint32_t     layer);

  void ws_command_clear_layer                ( uint32_t     layer);

  void ws_command_make_layer_visible         ( uint32_t     layer);

  void ws_command_make_layer_hidden          ( uint32_t     layer);

  void ws_command_set_layer_visibility       ( uint32_t     layer_mask);

  void ws_command_copy_layer_to_layer        ( uint32_t     layer_from,
					       uint32_t     layer_to);

  void ws_command_move_layer_to_layer        ( uint32_t     layer_from,
					       uint32_t     layer_to);

  void ws_command_beep                       ( uint32_t     beep_id);

  void ws_command_text_size                  ( const char*  text);


  void get_text_size                         ( const char* text, int& width, int& height);

  void wake_up_libwebsockets_from_another_thread();

  web_socket_connection(struct lws* wsi, int ID);
  ~web_socket_connection();
};


class web_interface : public runnable
{
  int port;
    
  struct lws_context_creation_info info;
  struct lws_context *context;
  
public:

  synchronized_data<bool> allow_deletion;

  int next_connection_ID;

  List<web_socket_connection> connections;


  void allow_deletions();
  void disallow_deletions();

  virtual void run();

  int  current_num_connections();
  
  web_interface(int port);
  ~web_interface();
};


#endif
