
// 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_DISPLAY_HH
#define WEB_DISPLAY_HH

#include "panel.hh"
#include "multiwindow_multipane.hh"
#include "display.hh"
#include "web_interface.hh"

//
// How layers are used from the web_interface, set with ws_command_set_current_layer(),
// for example.  Note that higher-numbered layers go on top of lower-numbered ones.
//
// LAYER_CURRENT_STATIC  -- currently displayed static layer
// LAYER_CURRENT_DYNAMIC -- currently displayed dynamic layer
//
// LAYER_DRAWING_STATIC  -- for drawing the next static layer (hidden)
// LAYER_DRAWING_DYNAMIC -- for drawing the next dynamic layer (hidden)
//

#define LAYER_BAD             -1  // Not actually used; set "current_layer" to this so set_current_layer() knows it's not set
#define LAYER_CURRENT_STATIC   0  // currently displayed
#define LAYER_CURRENT_DYNAMIC  1  // currently displayed
#define LAYER_DRAWING_STATIC   2
#define LAYER_DRAWING_DYNAMIC  3

class web_display : public display
{
  // Keep track of the current layer, so we don't have to send a command to switch it if it's the same.
  int current_layer;
  
  int mouse_buttons_pressed;
  
  static web_interface* wif;  // 0 until first checked for connection.

  int last_text_size;

  int draw_to_static;

  struct timeval   last_beep_time;

  //
  // From libInput input_event_handler.hh
  //
  // MOTION_STATE_NONE,     // Not handling motion, waiting a touch
  // MOTION_STATE_INIT,     // Got a touch, waiting 100ms to make sure another finger doesn't come down
  // MOTION_STATE_HOLDING,  // Have decided how many fingers, but no significant motion yet.  Send hold time events, flush minor motion.
  // MOTION_STATE_MOVING,   // Got a stable touch, moved away from it, processing move events
  // MOTION_STATE_ERROR,    // A finger was released on a multitouch event; sent a release to client, awaiting one from device, flushing all motion

  motion_state  touch_state;
  my_event      first_touch_me;
  my_event      last_touch_me;

  const static int      wait_for_fingers_us  = 500000;  // This needs to be a little larger, since web variability also affects it
  const static int      motion_to_break_hold = 30;  // web touch interfaces are generally small and high resolution.

public:

  struct timeval   last_event_time;        // For determining which display should be active

  web_socket_connection* wsc_active;
  int last_wsc_active_ID;

  virtual void   touch_recognized_beep();
  virtual void   entry_accepted_beep();
  virtual void   entry_rejected_beep();
  
  void           page_flip();
  void           copy_static_to_drawing();
  virtual bool   draw();

  void           get_and_process_events();

  virtual void   layout();

  virtual void   beginDrawToStatic()  { draw_to_static++; }
  virtual void   endDrawToStatic()    { draw_to_static--; }
  virtual void   clearDrawToStatic()  { draw_to_static=0; }

  //
  // Point, rect, and line drawing functions.
  //
  virtual void   draw_point_no_boundscheck(point p, color c);
  virtual color  get_point_no_boundscheck(point p);

  //
  // Text drawing functions.
  //
  // multicolor text uses ASCII x = 10 (0x0A) and up to indicate a switch to color
  // c[x-10].  Flags are in draw_text.hh
  //
  virtual void   draw_text(const char*     text, color c, int x, int y, int flags = 0);
  virtual void   draw_text(const wchar_t*  text, color c, int x, int y, int flags = 0);
  virtual void   draw_text(const char16_t* text, color c, int x, int y, int flags = 0);
  virtual void   draw_text(const char32_t* text, color c, int x, int y, int flags = 0);
  virtual void   draw_multicolored_text(const char*     text, int start_color, dt_colors& c, int x, int y, int flags = 0);
  virtual void   draw_multicolored_text(const wchar_t*  text, int start_color, dt_colors& c, int x, int y, int flags = 0);
  virtual void   draw_multicolored_text(const char16_t* text, int start_color, dt_colors& c, int x, int y, int flags = 0);
  virtual void   draw_multicolored_text(const char32_t* text, int start_color, dt_colors& c, int x, int y, int flags = 0);
  virtual int    calculate_text_width(const char*      text);
  virtual int    calculate_text_width(const wchar_t*   text);
  virtual int    calculate_text_width(const char16_t*  text);
  virtual int    calculate_text_width(const char32_t*  text);
  virtual int    calculate_text_height(const char*     text);
  virtual int    calculate_text_height(const wchar_t*  text);
  virtual int    calculate_text_height(const char16_t* text);
  virtual int    calculate_text_height(const char32_t* text);
  virtual void   set_text_size(int height_pixels);

  virtual void   draw_svg_from_data(uint8_t* svg_data, int svg_data_length, int offset_x=0, int offset_y=0, int draw_width=0, int draw_height=0);

  virtual void   clear_rect(color c, point corner1, point corner2);
  virtual void   draw_line_known_background(pointf p0, pointf p1, color c, color bg);
  virtual void   draw_line(pointf p0, pointf p1, color c);
  virtual void   draw_line_vertical(point p0, point p1, color c);
  virtual void   draw_line_horizontal(point p0, point p1, color c);
  virtual void   draw_circle(pointf p0, float radius, color c);
  virtual void   draw_triangle(pointf p0, pointf p1, pointf p2, color c);

  virtual void   draw_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);

  virtual void draw_ephemeral_alert(const char* text, int text_height, color c, color bg);

  virtual bool ready_for_more_graph_data();
  
  void handle_browser_connections();
  
  void set_current_layer(int layer);
  
  virtual ~web_display();
  web_display(int num_panes);
};

inline void web_display::set_current_layer(int layer)
{
  //printf("In SetCurrentLayer. wsc_active->ID=%d, last_wsc_active_ID=%d, layer=%d, current_layer=%d\n", wsc_active->ID, last_wsc_active_ID, layer, current_layer);
	 
  if(!wsc_active)
    return;
  
  if(layer!=current_layer)
    {
      wsc_active->ws_command_set_current_layer(layer);
      current_layer = layer;
    }
}


inline void web_display::draw_point_no_boundscheck(point p, color c)
{
  if(!wsc_active)
    return;
  
  if(draw_to_static)
    ;
  else
    ;
}

inline color web_display::get_point_no_boundscheck(point p)
{
  if(!wsc_active)
    return 0;

  if(draw_to_static)
    return 0;
  else
    return 0;
}

inline void web_display::clear_rect(color c, point corner1, point corner2)
{
  if(!wsc_active)
    return;

  int layer = draw_to_static ? LAYER_DRAWING_STATIC : LAYER_DRAWING_DYNAMIC;
  

  if(corner1.x<=0 && corner1.y<=0 && corner2.x>=width-1 && corner2.y>=height-1)
    {
      wsc_active->ws_command_clear_layer(layer);
    }
  else
    {
      set_current_layer(layer);
      wsc_active->ws_command_rectangle( RGB_to_RGBA(c), corner1.x, corner1.y, corner2.x, corner2.y);
    }
}



inline void web_display::draw_text(const char* text, color c, int x, int y, int flags)
{
  if(!wsc_active)
    return;

  //printf("Drawing text \"%s\" with draw_to_static=%d\n", text, draw_to_static);
  
  set_current_layer( draw_to_static ? LAYER_DRAWING_STATIC : LAYER_DRAWING_DYNAMIC );

  wsc_active->ws_command_text(RGB_to_RGBA(c), flags, x, y, text);
}

inline void web_display::draw_text(const wchar_t* text, color c, int x, int y, int flags)
{
  if(!wsc_active)
    return;

  //printf("Drawing text \"%s\" with draw_to_static=%d\n", text, draw_to_static);
  
  set_current_layer( draw_to_static ? LAYER_DRAWING_STATIC : LAYER_DRAWING_DYNAMIC );

  wsc_active->ws_command_text(RGB_to_RGBA(c), flags, x, y, text);
}

inline void web_display::draw_text(const char16_t* text, color c, int x, int y, int flags)
{
  if(!wsc_active)
    return;

  //printf("Drawing text \"%s\" with draw_to_static=%d\n", text, draw_to_static);
  
  set_current_layer( draw_to_static ? LAYER_DRAWING_STATIC : LAYER_DRAWING_DYNAMIC );

  wsc_active->ws_command_text(RGB_to_RGBA(c), flags, x, y, text);
}

inline void web_display::draw_text(const char32_t* text, color c, int x, int y, int flags)
{
  if(!wsc_active)
    return;

  //printf("Drawing text \"%s\" with draw_to_static=%d\n", text, draw_to_static);
  
  set_current_layer( draw_to_static ? LAYER_DRAWING_STATIC : LAYER_DRAWING_DYNAMIC );

  wsc_active->ws_command_text(RGB_to_RGBA(c), flags, x, y, text);
}


inline void web_display::draw_multicolored_text(const char* text, int start_color, dt_colors& c, int x, int y, int flags)
{
  uint32_t wc[MAX_COLORS];

  if(!wsc_active)
    return;
  
  for(int i=0; i<MAX_COLORS; i++)
    wc[i] = RGB_to_RGBA(c.c[i]);
  
  set_current_layer( draw_to_static ? LAYER_DRAWING_STATIC : LAYER_DRAWING_DYNAMIC );

  wsc_active->ws_command_multicolored_text(wc, start_color, flags, x, y, text);
}

inline void web_display::draw_multicolored_text(const wchar_t* text, int start_color, dt_colors& c, int x, int y, int flags)
{
  uint32_t wc[MAX_COLORS];

  if(!wsc_active)
    return;
  
  for(int i=0; i<MAX_COLORS; i++)
    wc[i] = RGB_to_RGBA(c.c[i]);
  
  set_current_layer( draw_to_static ? LAYER_DRAWING_STATIC : LAYER_DRAWING_DYNAMIC );

  wsc_active->ws_command_multicolored_text(wc, start_color, flags, x, y, text);
}

inline void web_display::draw_multicolored_text(const char16_t* text, int start_color, dt_colors& c, int x, int y, int flags)
{
  uint32_t wc[MAX_COLORS];

  if(!wsc_active)
    return;
  
  for(int i=0; i<MAX_COLORS; i++)
    wc[i] = RGB_to_RGBA(c.c[i]);
  
  set_current_layer( draw_to_static ? LAYER_DRAWING_STATIC : LAYER_DRAWING_DYNAMIC );

  wsc_active->ws_command_multicolored_text(wc, start_color, flags, x, y, text);
}

inline void web_display::draw_multicolored_text(const char32_t* text, int start_color, dt_colors& c, int x, int y, int flags)
{
  uint32_t wc[MAX_COLORS];

  if(!wsc_active)
    return;
  
  for(int i=0; i<MAX_COLORS; i++)
    wc[i] = RGB_to_RGBA(c.c[i]);
  
  set_current_layer( draw_to_static ? LAYER_DRAWING_STATIC : LAYER_DRAWING_DYNAMIC );

  wsc_active->ws_command_multicolored_text(wc, start_color, flags, x, y, text);
}


inline int  web_display::calculate_text_width(const char* text)
{

  if(!wsc_active)
    return 100;

  // Don't inquire text size from the remote server, since it takes too long on connections with high latency.
  // Instead, rely on the fact that local font metrics match the remote font metrics, so use the local width calculations.
#ifdef REMOTE_EXACT_SLOW_SIZING
  int width, height;
  wsc_active->get_text_size(text, width, height);
#else
  global_set_text_size(last_text_size);
  int width = global_calculate_text_width(text);
#endif
  
  //printf("Width of text \"%s\" is %d\n", text, width);

  return width;
  //return 100;
}


inline int  web_display::calculate_text_width(const wchar_t* text)
{

  if(!wsc_active)
    return 100;

  // Don't inquire text size from the remote server, since it takes too long on connections with high latency.
  // Instead, rely on the fact that local font metrics match the remote font metrics, so use the local width calculations.
#ifdef REMOTE_EXACT_SLOW_SIZING
  int width, height;
  wsc_active->get_text_size(text, width, height);
#else
  global_set_text_size(last_text_size);
  int width = global_calculate_text_width(text);
#endif
  
  //printf("Width of text \"%s\" is %d\n", text, width);

  return width;
  //return 100;
}


inline int  web_display::calculate_text_width(const char16_t* text)
{

  if(!wsc_active)
    return 100;

  // Don't inquire text size from the remote server, since it takes too long on connections with high latency.
  // Instead, rely on the fact that local font metrics match the remote font metrics, so use the local width calculations.
#ifdef REMOTE_EXACT_SLOW_SIZING
  int width, height;
  wsc_active->get_text_size(text, width, height);
#else
  global_set_text_size(last_text_size);
  int width = global_calculate_text_width(text);
#endif
  
  //printf("Width of text \"%s\" is %d\n", text, width);

  return width;
  //return 100;
}


inline int  web_display::calculate_text_width(const char32_t* text)
{

  if(!wsc_active)
    return 100;

  // Don't inquire text size from the remote server, since it takes too long on connections with high latency.
  // Instead, rely on the fact that local font metrics match the remote font metrics, so use the local width calculations.
#ifdef REMOTE_EXACT_SLOW_SIZING
  int width, height;
  wsc_active->get_text_size(text, width, height);
#else
  global_set_text_size(last_text_size);
  int width = global_calculate_text_width(text);
#endif
  
  //printf("Width of text \"%s\" is %d\n", text, width);

  return width;
  //return 100;
}


inline int  web_display::calculate_text_height(const char* text)
{
  if(!wsc_active)
    return 100;

  // Don't inquire text size from the remote server, since it takes too long on connections with high latency.
  // Instead, rely on the fact that local font metrics match the remote font metrics, so use the local height calculations.
#ifdef REMOTE_EXACT_SLOW_SIZING
  int width, height;
  wsc_active->get_text_size(text, width, height);
#else
  global_set_text_size(last_text_size);
  int height = global_calculate_text_height(text);
#endif

  return height;
  //return 20;
}


inline int  web_display::calculate_text_height(const wchar_t* text)
{
  if(!wsc_active)
    return 100;

  // Don't inquire text size from the remote server, since it takes too long on connections with high latency.
  // Instead, rely on the fact that local font metrics match the remote font metrics, so use the local height calculations.
#ifdef REMOTE_EXACT_SLOW_SIZING
  int width, height;
  wsc_active->get_text_size(text, width, height);
#else
  global_set_text_size(last_text_size);
  int height = global_calculate_text_height(text);
#endif

  return height;
  //return 20;
}

inline int  web_display::calculate_text_height(const char16_t* text)
{
  if(!wsc_active)
    return 100;

  // Don't inquire text size from the remote server, since it takes too long on connections with high latency.
  // Instead, rely on the fact that local font metrics match the remote font metrics, so use the local height calculations.
#ifdef REMOTE_EXACT_SLOW_SIZING
  int width, height;
  wsc_active->get_text_size(text, width, height);
#else
  global_set_text_size(last_text_size);
  int height = global_calculate_text_height(text);
#endif

  return height;
  //return 20;
}

inline int  web_display::calculate_text_height(const char32_t* text)
{
  if(!wsc_active)
    return 100;

  // Don't inquire text size from the remote server, since it takes too long on connections with high latency.
  // Instead, rely on the fact that local font metrics match the remote font metrics, so use the local height calculations.
#ifdef REMOTE_EXACT_SLOW_SIZING
  int width, height;
  wsc_active->get_text_size(text, width, height);
#else
  global_set_text_size(last_text_size);
  int height = global_calculate_text_height(text);
#endif

  return height;
  //return 20;
}


inline void web_display::set_text_size(int height_pixels)
{
  if(!wsc_active)
    return;

  if(height_pixels != last_text_size)
    {
      //printf("Font set to \"Helvetica\" %d\n", height_pixels);
      wsc_active->ws_command_font( "Helvetica", height_pixels);
      last_text_size = height_pixels;
    }
}


inline void web_display::draw_svg_from_data(uint8_t* svg_data, int svg_data_length, int offset_x, int offset_y, int draw_width, int draw_height)
{
  if(!wsc_active)
    return;

  set_current_layer( draw_to_static ? LAYER_DRAWING_STATIC : LAYER_DRAWING_DYNAMIC );

  wsc_active->ws_command_svg((const char*)svg_data, offset_x, offset_y, draw_width, draw_height);
}



inline void web_display::draw_line_known_background(pointf p0, pointf p1, color c, color bg)
{
  if(!wsc_active)
    return;

  set_current_layer( draw_to_static ? LAYER_DRAWING_STATIC : LAYER_DRAWING_DYNAMIC );

  wsc_active->ws_command_line(RGB_to_RGBA(c), 1.0, p0.x, p0.y, p1.x, p1.y);
}

inline void web_display::draw_line(pointf p0, pointf p1, color c)
{
  if(!wsc_active)
    return;

  set_current_layer( draw_to_static ? LAYER_DRAWING_STATIC : LAYER_DRAWING_DYNAMIC );

  wsc_active->ws_command_line(RGB_to_RGBA(c), 1.0, p0.x, p0.y, p1.x, p1.y);
}

inline void web_display::draw_line_vertical(point p0, point p1, color c)
{
  if(!wsc_active)
    return;

  set_current_layer( draw_to_static ? LAYER_DRAWING_STATIC : LAYER_DRAWING_DYNAMIC );

  wsc_active->ws_command_line(RGB_to_RGBA(c), 1.0, p0.x, p0.y, p1.x, p1.y);
}

inline void web_display::draw_line_horizontal(point p0, point p1, color c)
{
  if(!wsc_active)
    return;

  set_current_layer( draw_to_static ? LAYER_DRAWING_STATIC : LAYER_DRAWING_DYNAMIC );

  wsc_active->ws_command_line(RGB_to_RGBA(c), 1.0, p0.x, p0.y, p1.x, p1.y);
}

inline void web_display::draw_circle(pointf p0, float radius, color c)
{
  if(!wsc_active)
    return;

  wsc_active->ws_command_circle(RGB_to_RGBA(c), p0.x, p0.y, radius);
}


inline void web_display::draw_triangle(pointf p0, pointf p1, pointf p2, color c)
{
  if(!wsc_active)
    return;

  wsc_active->ws_command_triangle(RGB_to_RGBA(c), p0.x, p0.y, p1.x, p1.y, p2.x, p2.y);
}

inline void web_display::touch_recognized_beep()
{
  if(!wsc_active)
    return;

  struct timeval t;
  gettimeofday(&t, 0);

  long long time_since_last_beep_us = ( (t.tv_sec  - last_beep_time.tv_sec)*1000000ll +
					(t.tv_usec - last_beep_time.tv_usec) );

  if(time_since_last_beep_us>300000ll)
    {
      last_beep_time = t;
      wsc_active->ws_command_beep(BEEP_ID_TOUCH_RECOGNIZED);
    }
}
  
inline void web_display::entry_accepted_beep()
{
  if(!wsc_active)
    return;

  struct timeval t;
  gettimeofday(&t, 0);

  long long time_since_last_beep_us = ( (t.tv_sec  - last_beep_time.tv_sec)*1000000ll +
					(t.tv_usec - last_beep_time.tv_usec) );

  if(time_since_last_beep_us>300000ll)
    {
      last_beep_time = t;
      wsc_active->ws_command_beep(BEEP_ID_ENTRY_ACCEPTED);
    }
}

inline void web_display::entry_rejected_beep()
{
  if(!wsc_active)
    return;

  struct timeval t;
  gettimeofday(&t, 0);

  long long time_since_last_beep_us = ( (t.tv_sec  - last_beep_time.tv_sec)*1000000ll +
					(t.tv_usec - last_beep_time.tv_usec) );

  if(time_since_last_beep_us>300000ll)
    {
      last_beep_time = t;
      wsc_active->ws_command_beep(BEEP_ID_ENTRY_REJECTED);
    }
}

inline void web_display::draw_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)
{ 
  if(!wsc_active)
    return;

  set_current_layer( LAYER_DRAWING_DYNAMIC );

  wsc_active->ws_command_graph_data ( RGB_to_RGBA(color),
				      num_points,
				      x_start,
				      x_step,
				      x_at_left,
				      x_at_right,
				      y_at_bottom,
				      y_at_top,
				      x_offset,
				      y_offset,
				      width,
				      height,
				      y_data);
}




#endif
