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

#include "web_interface.hh"
#include "web_interface_webfiles.hh"


/* one of these is created for each client connecting to us */
struct per_session_data {
  web_socket_connection* connection;
  int bytes_sent_of_current_packet;
  int bytes_received_of_current_packet;
};


const static int max_bytes_per_write = 100000;



void print_packet_type(ws_command_packet* p)
{
  uint32_t type = p->type;

  if (type==WS_COMMAND_RECTANGLE)                  printf("rectangle\n");
  else if (type==WS_COMMAND_TRIANGLE)              printf("triangle\n");
  else if (type==WS_COMMAND_CIRCLE)                printf("circle\n");
  else if (type==WS_COMMAND_LINE)                  printf("line\n");
  else if (type==WS_COMMAND_GRAPH_DATA)            printf("graph_data\n");
  else if (type==WS_COMMAND_TEXT)                  { auto c = (ws_command_text*) p;                 printf("text \"%s\"\n", c->text); }
  else if (type==WS_COMMAND_FONT)                  printf("font\n");
  else if (type==WS_COMMAND_MULTICOLORED_TEXT)     printf("multicolored_text\n");
  else if (type==WS_COMMAND_SVG)                   printf("svg\n");
  else if (type==WS_COMMAND_SET_CURRENT_LAYER)     { auto c = (ws_command_set_current_layer*) p;    printf("set_current_layer %d\n", c->layer);   }
  else if (type==WS_COMMAND_CLEAR_LAYER)           { auto c = (ws_command_clear_layer*) p;          printf("clear_layer %d\n", c->layer);         }
  else if (type==WS_COMMAND_MAKE_LAYER_VISIBLE)    { auto c = (ws_command_make_layer_visible*) p;   printf("make_layer_visible %d\n", c->layer);  }
  else if (type==WS_COMMAND_MAKE_LAYER_HIDDEN)     { auto c = (ws_command_make_layer_hidden*) p;    printf("make_layer_hidden %d\n", c->layer);   }
  else if (type==WS_COMMAND_SET_LAYER_VISIBILITY)  { auto c = (ws_command_set_layer_visibility*) p; printf("set_layer_visibility 0x%08X\n", c->layer_mask); }
  else if (type==WS_COMMAND_COPY_LAYER_TO_LAYER)   { auto c = (ws_command_copy_layer_to_layer*) p;  printf("copy_layer_to_layer %d->%d\n", c->layer_from, c->layer_to); }
  else if (type==WS_COMMAND_MOVE_LAYER_TO_LAYER)   { auto c = (ws_command_move_layer_to_layer*) p;  printf("move_layer_to_layer %d->%d\n", c->layer_from, c->layer_to); }
  else if (type==WS_COMMAND_BEEP)                  printf("beep\n");
  else if (type==WS_COMMAND_TEXT_SIZE)             { auto c = (ws_command_text_size*) p;            printf("text_size \"%s\"\n", c->text); }
  else                                             printf("unknown_command %d\n", type);
}


int callback_bxb(struct lws *               wsi,
			enum lws_callback_reasons  reason,
			void *                     user,
			void *                     in,
			size_t                     len)
{
  web_interface* wif = (web_interface*) lws_context_user(lws_get_context(wsi));

  
  // handle a few cases up front because user data may not be defined, in which case we don't want to
  // dereference its pointer.


  switch(reason)
    {
    case LWS_CALLBACK_EVENT_WAIT_CANCELLED:
      {
	// This is called when another thread has data it wants to write, to break this thread out of its wait.
	// The other thread calls lws_cancel_service(context); and that gets this thread here.
	// Might want to call lws_cancel_service_pt(lws*) instead.
	//
	// Note that the wsi is bad here in that we can't call lws_callback_on_writable() on it.  We can
	// still use it to get the context.  So we need to walk through all the wsi's it might be
	// and wake them all so the correct one can write.  Nuts!
	//

	//printf("in event_wait_cancelled.  user=%p, wsi=%p, in=%p, len=%d, wif=%p\n", user, wsi, in, (int)len, wif);
	
	for(ListElement<web_socket_connection> * le = wif->connections.last(); le; le = le->previous())
	    {
	      web_socket_connection* wsc = le->data();

	      lws_callback_on_writable(wsc->wsi);
	    }
	
	return 0;
      }
    case LWS_CALLBACK_PROTOCOL_INIT:
      {
	return 0;
      }

    default:
      if(!user)
	{
	  printf("callback_bxb with reason=%d unexpectedly has zero user data pointer.\n", (int)reason);
	  return 0;
	}
    }
  
  //printf("In callback_bxb().\n"); fflush(stdout);

  struct per_session_data*  psd = (struct per_session_data *) user;
  web_socket_connection*    wsc = psd->connection;

    
  switch (reason)
    {      
    case LWS_CALLBACK_ESTABLISHED:
      psd->connection = new web_socket_connection(wsi, wif->next_connection_ID++);
      if(!psd->connection)
	{
	  printf("Can't allocate a new web_socket_connection.\n");
	  abort();
	}
      psd->bytes_sent_of_current_packet = 0;
      psd->bytes_received_of_current_packet = 0;
      wif->connections.addAtEnd(psd->connection, true);
      //printf("Added web_socket_connection %p, wsi %p\n", psd->connection, wsi);
      break;

    case LWS_CALLBACK_CLOSED:
      wsc->flag_delete_pending();
      wif->allow_deletion.lock();
      for(;;)
	{
	  if(wif->allow_deletion.data == true)
	    break;
	  wif->allow_deletion.wait_for_change();
	}
      wif->allow_deletion.unlock();
      wif->connections.remove(wsc);
      break;

    case LWS_CALLBACK_SERVER_WRITEABLE:
      {
	//printf("in LWS_CALLBACK_SERVER_WRITABLE for wsi=%p\n", wsi);

	if(wsc->need_to_disconnect)
	  {
	    return -1;
	  }

	bool have_command = wsc->command_is_on_command_queue();

	if(!have_command)
	  break;

	//if(!wsc->connected)
	//{
	//  //Just trash the commands.
	//  wsc->command_queue_out_position = (wsc->command_queue_out_position == WS_COMMAND_QUEUE_NUM_ITEMS-1) ? 0 : wsc->command_queue_out_position + 1;
	//  return 0;
	//}


	ws_command_packet*  packet           = (ws_command_packet*)wsc->command_queue[wsc->command_queue_out_position];

	
	
	//printf("  packet_type=%d, packet_length=%d\n", packet->type, packet->packet_length());
	
	uint8_t*            packet_start     = packet->packet_start();
	uint32_t            packet_length    = packet->packet_length();
	// On the javascript side, it's much easier to convert the binary data if it's always a multiple of 4
	// so it's an even number of 32-bit words.
	uint32_t            packet_length_round_up = ((packet_length+3)/4)*4;

	if(psd->bytes_sent_of_current_packet==0)
	{
	  for(uint32_t i=packet_length; i<packet_length_round_up; i++)
	    packet_start[i] = 0;
	}

	//printf("in LWS_CALLBACK_SERVER_WRITEABLE.  in_position=%d, out_position=%d bytes_total=%d, bytes_already_sent=%d ", wsc->command_queue_in_position, wsc->command_queue_out_position, packet_length, psd->bytes_sent_of_current_packet);
	//print_packet_type(packet);

	uint8_t*            this_write_start = packet_start + psd->bytes_sent_of_current_packet;
	uint32_t            remaining_length = packet_length_round_up - psd->bytes_sent_of_current_packet;

	uint32_t            bytes_this_write = (remaining_length<max_bytes_per_write) ? remaining_length : max_bytes_per_write;
	
	//printf("Sending packet of type %d with length %d rounded up to %d\n", (int)packet->type, (int)packet->length, packet_length_round_up);

	//for(int i=0; i<(int)packet_length_round_up; i++)
	//  printf("  packet[%d]=%d (%c)\n", i, (int)packet_start[i], packet_start[i]);
	
	/* LWS_PRE is included in the packet */

	uint32_t bytes_written;
	
	if(psd->bytes_sent_of_current_packet==0  && bytes_this_write == remaining_length)
	  {
	    // Write it all in one go
	    bytes_written = lws_write(wsi, this_write_start, bytes_this_write, LWS_WRITE_BINARY);
	  }
	else if(psd->bytes_sent_of_current_packet==0)
	  {
	    //printf("Sending packet start.\n");
	    // Start of a multi-part packet write
	    bytes_written = lws_write(wsi, this_write_start, bytes_this_write, lws_write_protocol(LWS_WRITE_BINARY | LWS_WRITE_NO_FIN));
	  }
	else if(bytes_this_write < remaining_length)
	  {
	    //printf("Sending middle section of packet.\n");
	    bytes_written = lws_write(wsi, this_write_start, bytes_this_write, lws_write_protocol(LWS_WRITE_CONTINUATION | LWS_WRITE_NO_FIN));
	  }
	else
	  {
	    //printf("Sending packet end.\n");
	    bytes_written = lws_write(wsi, this_write_start, bytes_this_write, LWS_WRITE_CONTINUATION);
	  }
	
	if (bytes_written < 0)
	  {
	    lwsl_err("ERROR %d writing to ws\n", bytes_written);
	    bytes_written = remaining_length;  // skip the rest of this command packet
	  }

	remaining_length -= bytes_written;
	psd->bytes_sent_of_current_packet += bytes_written;
	
	//printf("Sending packet of type %d\n", packet->type);

	if(remaining_length<=0)
	  {
	    wsc->command_queue_out_position = (wsc->command_queue_out_position == WS_COMMAND_QUEUE_NUM_ITEMS-1) ? 0 : wsc->command_queue_out_position + 1;
	    psd->bytes_sent_of_current_packet = 0;
	  }
	
	if(remaining_length>0 || wsc->command_is_on_command_queue())
	  lws_callback_on_writable(wsi);
      }
      break;
      
    case LWS_CALLBACK_RECEIVE:
      {
	int next_event_input_position;

	//printf("got receive callback; wsi=%p.  wsc->wsi=%p\n", wsi, wsc->wsi);
	
	// Wait until we have room on the queue.
	for(;;)
	  {
	    next_event_input_position = wsc->next_input_position_for_event_queue_wait();
	    if(next_event_input_position>=0)
	      break;
	  }
	
	ws_event_packet* packet = (ws_event_packet*) wsc->event_queue[wsc->event_queue_in_position];

	uint32_t total_length_so_far = len + psd->bytes_received_of_current_packet;
	
	if(total_length_so_far > WS_EVENT_QUEUE_MAX_LENGTH)
	  {
	    printf("Packet length %d exceeds maximum allowed length %d.  Dropping packet.\n", (int)len, WS_EVENT_QUEUE_MAX_LENGTH);

	    if(lws_is_final_fragment(wsi))
	      psd->bytes_received_of_current_packet = 0;
	    else
	      psd->bytes_received_of_current_packet = total_length_so_far;

	    break;
	  }
	
	memcpy(((char *)packet)+psd->bytes_received_of_current_packet, in, len);

	
	if(lws_is_final_fragment(wsi))
	  {
	    psd->bytes_received_of_current_packet = 0;
	  }
	else
	  {
	    psd->bytes_received_of_current_packet = total_length_so_far;
	    break;
	  }
	
	//printf("packet type is %d\n", packet->type);

	if(total_length_so_far != packet->length)
	  {
	    printf("Packet length %d mismatches embedded length %d.  Bad packet (type might be %d).\n", (int)len, packet->length, packet->type);
	    //break;

	    const char* text_message   = (char*)in;

	    //for(int i=0; i<len; i++)
	    //  printf("  %d: %d %c\n", i, (int)text_message[i], text_message[i]);

	    printf("Text message: \"");
	    for(int i=0; i<(int)len; i++)
	      printf("%c", text_message[i]);
	    printf("\"\n");
	    break;
	  }
	    //printf("Packet length %d matches embedded length %d.  Good packet.\n", (int)len, packet->length);

	// These event types take actions independent of the queue.  Some skip the queue
	if(packet->type == WS_EVENT_TEXT_SIZE)
	  {
	    // text size packets are being waited for; they skip the queue
	    auto event = (ws_event_text_size*)packet;

	    wsc->ts.lock();
	    wsc->ts.data.width  = event->width;
	    wsc->ts.data.height = event->height;
	    wsc->ts.unlock();
	    break;  // Skip the queue
	  }
	else if(packet->type == WS_EVENT_RESIZE)
	  {
	    // resize size packets are being waited for; they skip the queue
	    auto event = (ws_event_resize*)packet;

	    //printf("RESIZE packet to size %dx%d\n", event->width, event->height);

	    if(event->width>100 && event->width<20000 && event->height>100 && event->height<20000)
	      {
		wsc->width     = event->width;
		wsc->height    = event->height;
		wsc->connected = true;
	      }
	    else
	      {
		// Got insane values.  Something wrong.  Abort.
		wsc->need_to_disconnect = true;
	      }
	    lws_callback_on_writable(wsi);
	  }
	else if (packet->type == WS_EVENT_GRAPH_COUNT_UPDATE)
	  {
	    auto event = (ws_event_graph_count_update*)packet;
	    wsc->last_graph_count_received =  event->graph_count;
	    //printf("got graph_count_update to %d\n", event->graph_count);
	    break; // skip the queue
	  }
	
	
	//printf("event packet position changed from %d to %d.\n", wif->event_queue_in_position, next_event_input_position);
	
	wsc->event_queue_in_position = next_event_input_position;
      }
      break;
	
    default:
      break;
    }
  
  return 0;
}





static struct lws_protocols protocols[] =
  {
   // name        callback            per_session_data_size         rx_buffer_size  id  user_ptr   tx_packet_size
   { "http",    callback_http,    sizeof(struct http_progress),             0,       0,   NULL,          0  },
   { "lws-bxb", callback_bxb,     sizeof(struct per_session_data),        128,       0,   NULL,          0  },
   { NULL, NULL, 0, 0, 0, NULL, 0 } /* terminator */
  };


web_interface::web_interface(int port)
{
  this->port = port;
  this->next_connection_ID = 0;
  start();
}

void web_interface::allow_deletions()
{
  allow_deletion.lock();
  allow_deletion.data = true;
  allow_deletion.unlock();
}

void web_interface::disallow_deletions()
{
  allow_deletion.lock();
  allow_deletion.data = false;
  allow_deletion.unlock();
}



int web_interface::current_num_connections()
{
  return connections.numInList();
}


web_socket_connection::web_socket_connection(struct lws* wsi, int ID)
{
  this->ID = ID;
  this->wsi = wsi;

  width = 1920;
  height = 1080;

  need_to_disconnect = false;
  connected = false; // Only changed once we get width/height information

  last_graph_count_sent      = 0;
  last_graph_count_received  = 0;

  command_queue_in_position = 0;
  command_queue_out_position = 0;
  event_queue_in_position = 0;
  event_queue_out_position = 0;

  // Make sure there's no way that text_size info can get by uninitialized
  ts.lock();
  ts.data.width  = 100;
  ts.data.height = 20;
  ts.unlock();
}

web_socket_connection::~web_socket_connection()
{
  // Make sure any wait for text size over the web socket connection is canceled.
  connected = false;
  usleep(100);
  ts.lock();
  ts.unlock();
  usleep(100);
}


void web_interface::run()
{
  int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;

  memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
  info.port        = port;
  info.protocols   = protocols;
  info.vhost_name  = "RFSOC";
  info.options     = 0; //LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;  // This didn't allow manifests.  Added my own security header in procotol_http.cc
  info.pt_serv_buf_size = 128*1024;
  info.user        = this;
  
  //signal(SIGINT, sigint_handler);
  //
  //if((p = lws_cmdline_option(argc, argv, "-d")))
  //  logs = atoi(p);
  //
  //if(lws_cmdline_option(argc, argv, "-h"))
  //  info.options |= LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK;
  
  lws_set_log_level(logs, NULL);
  //lwsl_user("LWS minimal ws server: visit http://localhost\n");
	
  context = lws_create_context(&info);
  if(!context)
    {
      lwsl_err("lws init failed\n");
      exit(10);
    }

  for(;;)
    {
      int status = lws_service(context, 0);
      if(status < 0)
	break;
    }
}


web_interface::~web_interface()
{
  cancel();
  wait_for_completion();
  lws_context_destroy(context);
}


bool web_socket_connection::command_is_on_command_queue()
{
  return (command_queue_in_position != command_queue_out_position);
}


int web_socket_connection::num_commands_on_command_queue()
{
  return (command_queue_in_position + WS_COMMAND_QUEUE_NUM_ITEMS - command_queue_out_position) % WS_COMMAND_QUEUE_NUM_ITEMS;
}


bool web_socket_connection::event_is_on_event_queue()
{
  return (event_queue_in_position != event_queue_out_position);
}



int web_socket_connection::next_input_position_for_command_queue()
{
  int next_ip = (command_queue_in_position == WS_COMMAND_QUEUE_NUM_ITEMS-1) ? 0 : command_queue_in_position + 1;

  if( next_ip != command_queue_out_position)
    return next_ip;
  else
    return -1;
}


int web_socket_connection::next_input_position_for_command_queue_wait()
{
  int next_ip;

  if(need_to_disconnect)
    return 0;
  
  for(int i=0;;i++)
    {
      next_ip = next_input_position_for_command_queue();
      if(next_ip>=0)
	break;
      if(i==2)
	{
	  printf("WS command queue full, blocked at least twice.\n");
	}
      else if(i==3000)
	{
	  printf("WS command queue blocked too long -- closing connection.\n");
	  need_to_disconnect = true;
	  lws_cancel_service(lws_get_context(wsi));
	  next_ip = 0;
	  break;
	}
	
      usleep(1000);
      if(!connected)
	{
	  next_ip = 0;  // return something innocuous.  This is going away anyway.
	  break;
	}
    }

  return next_ip;
}

int web_socket_connection::next_input_position_for_event_queue()
{
  int next_ip = (event_queue_in_position == WS_EVENT_QUEUE_NUM_ITEMS-1) ? 0 : event_queue_in_position + 1;

  if( next_ip != event_queue_out_position)
    return next_ip;
  else
    return -1;
}


int web_socket_connection::next_input_position_for_event_queue_wait()
{
  int next_ip;
  
  for(int i=0;;i++)
    {
      next_ip = next_input_position_for_event_queue();
      if(next_ip>=0)
	break;

      if(i==2)
	{
	  printf("WS event queue full, blocked at least twice.\n");
	}
      else if(i==3000)
	{
	  printf("WS connection somehow overwhelming us with events -- closing connection.\n");
	  need_to_disconnect = true;
	  lws_cancel_service(lws_get_context(wsi));
	  next_ip = 0;
	  break;
	}
      usleep(1000);
    }

  return next_ip;
}


void web_socket_connection::wake_up_libwebsockets_from_another_thread()
{
  //  if(wsi)
  //    lws_callback_on_writable(wsi);

  //printf("Canceling service.\n");

  if(connected)
    lws_cancel_service(lws_get_context(wsi));

  //lws_cancel_service_pt(wsi);
  
  //printf("wake_up_libwebsockets.\n"); fflush(stdout);
}


void web_socket_connection::ws_command_rectangle  ( uint32_t     color,
					    float        x1,
					    float        y1,
					    float        x2,
					    float        y2)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_rectangle*) command_queue[command_queue_in_position];

  p->type   = WS_COMMAND_RECTANGLE;
  p->length = sizeof(*p) - LWS_PRE;
  p->color  = color;
  p->x1     = x1;
  p->y1     = y1;
  p->x2     = x2;
  p->y2     = y2;

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}

void web_socket_connection::ws_command_triangle   ( uint32_t     color,
					    float        x1,
					    float        y1,
					    float        x2,
					    float        y2,
					    float        x3,
					    float        y3)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_triangle*) command_queue[command_queue_in_position];

  p->type   = WS_COMMAND_TRIANGLE;
  p->length = sizeof(*p) - LWS_PRE;
  p->color  = color;
  p->x1     = x1;
  p->y1     = y1;
  p->x2     = x2;
  p->y2     = y2;
  p->x3     = x3;
  p->y3     = y3;

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}

void web_socket_connection::ws_command_circle     ( uint32_t     color,
					    float        center_x,
					    float        center_y,
					    float        radius)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_circle*) command_queue[command_queue_in_position];

  p->type      = WS_COMMAND_CIRCLE;
  p->length    = sizeof(*p) - LWS_PRE;
  p->color     = color;
  p->center_x  = center_x;
  p->center_y  = center_y;
  p->radius    = radius;

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}

void web_socket_connection::ws_command_line       ( uint32_t     color,
					    float        width,
					    float        x1,
					    float        y1,
					    float        x2,
					    float        y2)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_line*) command_queue[command_queue_in_position];

  p->type   = WS_COMMAND_LINE;
  p->length = sizeof(*p)  - LWS_PRE;
  p->color  = color;
  p->width  = width;
  p->x1     = x1;
  p->y1     = y1;
  p->x2     = x2;
  p->y2     = y2;

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}

static long long get_time_in_us()
{
  struct timeval t;
  gettimeofday(&t, 0);
  long long t_us = t.tv_sec*1000000ll + t.tv_usec;

  return t_us;
}

bool web_socket_connection::ready_for_more_graph_data()
{
  static long long last_time = 0;
  //  return (last_graph_count_received==0) || (last_graph_count_received >= last_graph_count_sent - 2);
  //bool ready =  (last_graph_count_received == last_graph_count_sent) || (last_graph_count_received == last_graph_count_sent-1);
  bool ready =  (last_graph_count_received == last_graph_count_sent);

  //printf("last_graph_count_received=%d last_graph_count_sent=%d\n", last_graph_count_received, last_graph_count_sent);
  
  if(ready)
    {
      last_time = get_time_in_us();
    }
  else if ( get_time_in_us() - last_time >5000000 )
    {
      // timeout.  Something wrong happened.  Mop up.
      //printf("***************************************Lost graph timeout.  Sent count %d, but last received was %d\n", last_graph_count_sent, last_graph_count_received);
      //last_graph_count_received = last_graph_count_sent;
      //last_time = get_time_in_us();
      //ready = true;
      need_to_disconnect = true;
      lws_cancel_service(lws_get_context(wsi));
    }

  return ready;
}


void web_socket_connection::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)
{  
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_graph_data*) command_queue[command_queue_in_position];

  p->type         = WS_COMMAND_GRAPH_DATA;
  p->length       = sizeof(*p) + num_points * sizeof(data_point)  - LWS_PRE;
  p->color        = color;
  p->num_points   = num_points;
  p->x_start      = x_start;
  p->x_step       = x_step;
  p->x_at_left    = x_at_left;
  p->x_at_right   = x_at_right;
  p->y_at_bottom  = y_at_bottom;
  p->y_at_top     = y_at_top;
  p->x_offset     = x_offset;
  p->y_offset     = y_offset;
  p->width        = width;
  p->height       = height;
  p->graph_count  = ++last_graph_count_sent;
  
  for(int i=0; i<num_points; i++)
    p->y_data[i] = y_data[i];

  command_queue_in_position = next_ip;

  //printf("sending graph count %d\n", p->graph_count);
  
  wake_up_libwebsockets_from_another_thread();
}



void web_socket_connection::ws_command_text ( uint32_t     color,
					      uint32_t     flags,
					      float        x,
					      float        y,
					      const char*  text)
{
  int length = 0;
  while(text[length])
    length++;

  char32_t ucode[length+1];

  for(int i=0; i<=length; i++)
    ucode[i] = text[i];

  ws_command_text ( color,
		    flags,
		    x,
		    y,
		    ucode);
}

void web_socket_connection::ws_command_text ( uint32_t        color,
					      uint32_t        flags,
					      float           x,
					      float           y,
					      const wchar_t*  text)
{
  int length = 0;
  while(text[length])
    length++;

  char32_t ucode[length+1];

  for(int i=0; i<=length; i++)
    ucode[i] = text[i];

  ws_command_text ( color,
		    flags,
		    x,
		    y,
		    ucode);
}

void web_socket_connection::ws_command_text ( uint32_t         color,
					      uint32_t         flags,
					      float            x,
					      float            y,
					      const char16_t*  text)
{
  int length = 0;
  while(text[length])
    length++;

  char32_t ucode[length+1];

  for(int i=0; i<=length; i++)
    ucode[i] = text[i];

  ws_command_text ( color,
		    flags,
		    x,
		    y,
		    ucode);
}

void web_socket_connection::ws_command_text ( uint32_t         color,
					      uint32_t         flags,
					      float            x,
					      float            y,
					      const char32_t*  text)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_text*) command_queue[command_queue_in_position];

  int length = 0;
  while(text[length])
    length++;

  p->type         = WS_COMMAND_TEXT;
  p->length       = sizeof(*p) + 4*length - LWS_PRE + 1;
  p->color        = color;
  p->flags        = flags;
  p->x            = x;
  p->y            = y;
  p->text_length  = length;

  for(int i=0; i<length; i++)
    ((char32_t*)p->text)[i] = text[i];

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}


void web_socket_connection::ws_command_multicolored_text ( uint32_t     colors[MAX_COLORS],
							   int          start_color,
							   uint32_t     flags,
							   float        x,
							   float        y,
							   const char*  text)
{
  int length = 0;
  while(text[length])
    length++;

  char32_t ucode[length+1];

  for(int i=0; i<=length; i++)
    ucode[i] = text[i];

  ws_command_multicolored_text ( colors,
				 start_color,
				 flags,
				 x,
				 y,
				 ucode);
}

void web_socket_connection::ws_command_multicolored_text ( uint32_t        colors[MAX_COLORS],
							   int             start_color,
							   uint32_t        flags,
							   float           x,
							   float           y,
							   const wchar_t*  text)
{
  int length = 0;
  while(text[length])
    length++;

  char32_t ucode[length+1];

  for(int i=0; i<=length; i++)
    ucode[i] = text[i];

  ws_command_multicolored_text ( colors,
				 start_color,
				 flags,
				 x,
				 y,
				 ucode);
}

void web_socket_connection::ws_command_multicolored_text ( uint32_t         colors[MAX_COLORS],
							   int              start_color,
							   uint32_t         flags,
							   float            x,
							   float            y,
							   const char16_t*  text)
{
  int length = 0;
  while(text[length])
    length++;

  char32_t ucode[length+1];

  for(int i=0; i<=length; i++)
    ucode[i] = text[i];

  ws_command_multicolored_text ( colors,
				 start_color,
				 flags,
				 x,
				 y,
				 ucode);
}

void web_socket_connection::ws_command_multicolored_text ( uint32_t         colors[MAX_COLORS],
							   int              start_color,
							   uint32_t         flags,
							   float            x,
							   float            y,
							   const char32_t*  text)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_multicolored_text*) command_queue[command_queue_in_position];

  int length = 0;
  while(text[length])
    length++;

  //printf("ws_command_multicolored_text.  text=\"%s\"m len=%d.\n", text, (int)strlen(text));
  
  p->type         = WS_COMMAND_MULTICOLORED_TEXT;
  p->length       = sizeof(*p) + 4*(1+length) - LWS_PRE + 1;

  for(int i=0; i<MAX_COLORS; i++)
    p->colors[i] = colors[i];
  p->flags        = flags;
  p->x            = x;
  p->y            = y;
  p->text_length  = length+1;
  ((char32_t*)(p->text))[0]      = 10 + start_color;

  for(int i=0; i<length; i++)
    ((char32_t*)p->text)[1+i] = text[i];

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}




void web_socket_connection::ws_command_font       ( const char*  font_name,
						    int          size_pts)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_font*) command_queue[command_queue_in_position];

  p->type             = WS_COMMAND_FONT;
  p->length           = sizeof(*p) + strlen(font_name) - LWS_PRE + 1;
  p->font_size        = size_pts;
  p->font_name_length = strlen(font_name);

  strcpy((char*)p->font_name, font_name);

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}



void web_socket_connection::ws_command_svg       ( const char*  svg_data,
						   float        offset_x,
						   float        offset_y,
						   float        draw_width,
						   float        draw_height)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_svg*) command_queue[command_queue_in_position];

  //  svg_data = "<circle fill=\"rgba(48,52,48,1)\" cx=\"422\" cy=\"414\" r=\"200\"></circle>";
  
  int data_length = strlen(svg_data);
  
  p->type             = WS_COMMAND_SVG;
  p->length           = sizeof(*p) + data_length - LWS_PRE + 1;
  p->x                = offset_x;
  p->y                = offset_y;
  p->width            = draw_width;
  p->height           = draw_height;
  p->text_length      = data_length;

  if(p->text_length > WS_COMMAND_QUEUE_MAX_LENGTH - 64)
    {
      printf("SVG file is too long for current settings.\n");
      exit(20);
    }
       
  strcpy((char*)p->text, svg_data);

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}


void web_socket_connection::ws_command_set_current_layer          ( uint32_t     layer)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_set_current_layer*) command_queue[command_queue_in_position];

  p->type     = WS_COMMAND_SET_CURRENT_LAYER;
  p->length   = sizeof(*p) - LWS_PRE;
  p->layer    = layer;

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}

void web_socket_connection::ws_command_clear_layer                ( uint32_t     layer)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_clear_layer*) command_queue[command_queue_in_position];

  p->type     = WS_COMMAND_CLEAR_LAYER;
  p->length   = sizeof(*p) - LWS_PRE;
  p->layer    = layer;

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}

void web_socket_connection::ws_command_make_layer_visible         ( uint32_t     layer)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_make_layer_visible*) command_queue[command_queue_in_position];

  p->type     = WS_COMMAND_MAKE_LAYER_VISIBLE;
  p->length   = sizeof(*p) - LWS_PRE;
  p->layer    = layer;

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}

void web_socket_connection::ws_command_make_layer_hidden          ( uint32_t     layer)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_make_layer_hidden*) command_queue[command_queue_in_position];

  p->type     = WS_COMMAND_MAKE_LAYER_HIDDEN;
  p->length   = sizeof(*p) - LWS_PRE;
  p->layer    = layer;

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}


void web_socket_connection::ws_command_set_layer_visibility       ( uint32_t     layer_mask)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_set_layer_visibility*) command_queue[command_queue_in_position];

  p->type        = WS_COMMAND_SET_LAYER_VISIBILITY;
  p->length      = sizeof(*p) - LWS_PRE;
  p->layer_mask  = layer_mask;

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}

void web_socket_connection::ws_command_copy_layer_to_layer ( uint32_t     layer_from,
							     uint32_t     layer_to)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_copy_layer_to_layer*) command_queue[command_queue_in_position];

  p->type        = WS_COMMAND_COPY_LAYER_TO_LAYER;
  p->length      = sizeof(*p) - LWS_PRE;
  p->layer_from  = layer_from;
  p->layer_to    = layer_to;

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}


void web_socket_connection::ws_command_move_layer_to_layer ( uint32_t     layer_from,
							     uint32_t     layer_to)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_move_layer_to_layer*) command_queue[command_queue_in_position];

  p->type        = WS_COMMAND_MOVE_LAYER_TO_LAYER;
  p->length      = sizeof(*p) - LWS_PRE;
  p->layer_from  = layer_from;
  p->layer_to    = layer_to;

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}


void web_socket_connection::ws_command_beep       ( uint32_t     beep_id)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_beep*) command_queue[command_queue_in_position];

  p->type     = WS_COMMAND_BEEP;
  p->length   = sizeof(*p) - LWS_PRE;
  p->beep_id  = beep_id;

  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}

void web_socket_connection::ws_command_text_size  ( const char*  text)
{
  int next_ip = next_input_position_for_command_queue_wait();

  auto p = (struct ws_command_text_size*) command_queue[command_queue_in_position];

  int txtsize = strlen(text);

  p->type        = WS_COMMAND_TEXT_SIZE;
  p->text_length = txtsize;
  p->length      = sizeof(*p) + txtsize  - LWS_PRE + 1;
  strcpy((char*)p->text, text);
  
  command_queue_in_position = next_ip;

  wake_up_libwebsockets_from_another_thread();
}



void web_socket_connection::get_text_size ( const char* text, int& width, int& height)
{
  ws_command_text_size(text);

  long sec   = 2;
  long nsec  = 0;
  int  error = 1;
  
  ts.lock();
  if(connected)
    {
      //ts.wait_for_change();
      error = ts.wait_for_change(sec, nsec);
    }
  ts.unlock();

  if(error)
    {
      width = 100;
      height = 20;
    }
  else
    {
      width = ts.data.width;
      height = ts.data.height;
    }
}
