
// 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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "displays.hh"
#include "color.hh"
#include "hotspot.hh"
#include "web_display.hh"

//void create_display_panels();
//#define run_at_start(function) class at_start { public: at_start(void (*func)()) { func(); }; } run(function)
//run_at_start(create_display_panels);


//#define DEBUG_TOUCH

web_interface* web_display::wif = 0;


#define memclear(s)  memset(&s, 0, sizeof(s))
#define VOIDP2U64(x) ((uint64_t)(void*)(x))


void set_up_inactive_page(web_socket_connection* wsc)
{
  uint32_t color = RGB_to_RGBA(BLUE);

  wsc->ws_command_font( "Helvetica", 40);

  wsc->ws_command_set_layer_visibility( (1<<LAYER_CURRENT_STATIC) | (1<<LAYER_CURRENT_DYNAMIC) );

  for(int i=0; i<WS_MAX_LAYERS; i++)
    wsc->ws_command_clear_layer(i);

  wsc->ws_command_set_current_layer(LAYER_CURRENT_STATIC);

  wsc->ws_command_text(color, DRAW_TEXT_X_CENTER|DRAW_TEXT_Y_CENTER|DRAW_TEXT_ROTATE_0, wsc->width/2, wsc->height/2-60, "Some other display is active.");
  wsc->ws_command_text(color, DRAW_TEXT_X_CENTER|DRAW_TEXT_Y_CENTER|DRAW_TEXT_ROTATE_0, wsc->width/2, wsc->height/2, "Click to reclaim.");
}


void print_touch_event(ws_event_packet* packet, motion_state touch_state, long long t_us)
{
  const char* motion_state_name;
  
  if(touch_state==MOTION_STATE_NONE)
    motion_state_name = "MOTION_STATE_NONE";
  else if(touch_state==MOTION_STATE_INIT)
    motion_state_name = "MOTION_STATE_INIT";
  else if(touch_state==MOTION_STATE_HOLDING)
    motion_state_name = "MOTION_STATE_HOLDING";
  else if(touch_state==MOTION_STATE_MOVING)
    motion_state_name = "MOTION_STATE_MOVING";
  else if(touch_state==MOTION_STATE_ERROR)
    motion_state_name = "MOTION_STATE_ERROR";
  else
    motion_state_name = "INVALID_MOTION_STATE";

  const char* pktname = 0;
  if(packet->type == WS_EVENT_TOUCHSTART)
    {
      pktname = "TOUCHSTART";
    }
  else if(packet->type == WS_EVENT_TOUCHEND)
    {
      pktname = "TOUCHEND";
    }
  else if(packet->type == WS_EVENT_TOUCHMOVE)
    {
      pktname = "TOUCHMOVE";
    }
  else if(packet->type == WS_EVENT_TOUCHCANCEL)
    {
      pktname = "TOUCHCANCEL";
    }

  if(!pktname)
    {
      printf("Got unknown touch packet type.\n");
      return;
    }

  auto event = (ws_event_touch*)packet;

  printf("In state %s, got %s packet with %d touches: ", motion_state_name, pktname, event->num_touches);

  for(unsigned int i=0; i<event->num_touches; i++)
    {
      printf("(%d,%d) ", event->touches[i].x, event->touches[i].y);
    }

  printf(" time %lld\n", t_us);
}
    

void web_display::handle_browser_connections()
{
  wif->allow_deletions();
  usleep(1000);
  wif->disallow_deletions();

  if(!wif->connections.isInList(wsc_active))
    {
      // wsc_active may have be null, or may have just been closed:  it's not on the list.

      ListElement<web_socket_connection>* le = wif->connections.last();
      if(!le)
	{
	  // There's nothing on the list.  Return with no active connection
	  //printf("wsc_active %p not on list.\n", wsc_active);
	  wsc_active = 0;
	  last_wsc_active_ID = -1;
	  return;
	}

      // There's at least one other item on the list.  Set it as active.
      wsc_active = le->data();
    }

  // If we get to here, we should have a valid wsc_active
  
  if(!wsc_active->connected)
    {
      //printf("wsc_active %p not on connectedq.\n", wsc_active);
      wsc_active = 0;
      last_wsc_active_ID = -1;
      return;
    }

  if(wsc_active->ID != last_wsc_active_ID)
    {
      //printf("ID mismatch.  wsc_active has changed.  Redraw.\n");
      // Force a complete redraw
      //wsc_active->ws_command_set_current_layer(layer);
      current_layer = LAYER_BAD;
      resize(0, 0, wsc_active->width, wsc_active->height);
      layout_dirty = true;
      last_wsc_active_ID = wsc_active->ID;
      for(ListElement<web_socket_connection>* le = wif->connections.last(); le; le = le->previous())
	{
	  web_socket_connection* wsc = le->data();

	  //printf("wsc=%p\n", wsc);
	  
	  if(wsc != wsc_active)
	    set_up_inactive_page(wsc);
	}

      last_text_size = 0;
    }
}


void web_display::layout()
{
  int xmargin = 0;
  int ymargin = 0;

  clear(bgcolor);

  layout_calculator->do_layout(0, 0,
			       width, height,
			       xmargin, ymargin,
			       *subwindows,
			       target_aspect_x,
			       target_aspect_y,
			       force_aspect);
  
  layout_dirty = false;
  dirty = true;
}




void web_display::copy_static_to_drawing()
{
  //printf("\n\n\n\ncopy_static_to_drawing enter layout_dirty=%d, dirty=%d\n", layout_dirty, dirty);

  //wsc_active->ws_command_make_layer_hidden( LAYER_DRAWING_STATIC );

  if(layout_dirty)
    {
      // Should also set background color here to bgcolor
      //wif->ws_command_clear_layer(LAYER_DRAWING_STATIC);

      //printf("web_display Layout is dirty.\n");

      wsc_active->ws_command_set_layer_visibility( (1<<LAYER_CURRENT_STATIC) | (1<<LAYER_CURRENT_DYNAMIC) );

      beginDrawToStatic();
      layout();
      endDrawToStatic();
      layout_dirty = false;
      dirty        = true;
    }
  //printf("copy_static_to_drawing layout done layout_dirty=%d, dirty=%d\n", layout_dirty, dirty);
  if(dirty)
    {
      //printf("web_display is dirty.\n");
      beginDrawToStatic();
      draw_dirty();
      endDrawToStatic();
      dirty = false;
      wsc_active->ws_command_copy_layer_to_layer(LAYER_DRAWING_STATIC, LAYER_CURRENT_STATIC);

      //wsc_active->ws_command_set_layer_visibility( (1<<LAYER_DRAWING_STATIC) | (1<<LAYER_CURRENT_DYNAMIC) );
      //wsc_active->ws_command_set_layer_visibility( (1<<LAYER_DRAWING_STATIC) );

      //wsc_active->ws_command_exchange_layer_visibility(LAYER_TEMP_STATIC,  // newly_visible_layer
      //						       LAYER_CURRENT_STATIC); // newly_hidden_layer
      //wsc_active->ws_command_swap_layer_numbers(LAYER_TEMP_STATIC,
      //					LAYER_CURRENT_STATIC);
      //wsc_active->ws_command_make_layer_visible( LAYER_DRAWING_STATIC );
    }
  
  //printf("copy_static_to_drawing exit layout_dirty=%d, dirty=%d\n\n\n\n", layout_dirty, dirty);

  // This is part of a page flip
  //wsc_active->ws_command_copy_layer_to_layer(LAYER_DRAWING_STATIC, LAYER_CURRENT_STATIC);

  //wsc_active->ws_command_make_layer_visible( LAYER_CURRENT_STATIC );
}


bool web_display::ready_for_more_graph_data()
{
  if(wsc_active)
    return wsc_active->ready_for_more_graph_data();
  else
    return false;
}


bool web_display::draw()
{
  if(!wsc_active)
    return true;
  
  //  if(wsc_active->command_is_on_command_queue())
  //  return;


  //
  // Don't let too many packets accumulate, or we become unresponsive.
  // On the other hand, don't let too few accumulate, or the queue runs
  // dry and we lose performance.
  //
  //if(wsc_active->num_commands_on_command_queue()>0)
  //  return false;

  if(!wsc_active->ready_for_more_graph_data())
    {
      //printf("                            not ready\n");
      return false;
    }

  //printf("draw!\n");
  
  //if(wif->num_commands_on_command_queue()>10)
  //  return;

  //  static int flip_counter = 0;

 
  //wsc_active->ws_command_set_layer_visibility( 31 );

  copy_static_to_drawing();

  //if(flip_counter++>100)
  //{
      //printf("draw_dynamic start\n");
  draw_dynamic();
      //printf("draw_dynamic end\n");
  page_flip();
      //flip_counter=0;
      //}

  usleep(10000);
  return true;
}

void web_display::page_flip()
{
  ////wif->ws_command_exchange_layer_visibility(LAYER_DRAWING_STATIC,  // newly_visible_layer
  ////					    LAYER_CURRENT_STATIC); // newly_hidden_layer
  ////wif->ws_command_swap_layer_numbers(LAYER_DRAWING_STATIC,
  ////				     LAYER_CURRENT_STATIC);
  ////wif->ws_command_clear_layer(LAYER_DRAWING_STATIC);
  ////

  
  //wsc_active->ws_command_exchange_layer_visibility(LAYER_DRAWING_DYNAMIC,  // newly_visible_layer
  //						   LAYER_CURRENT_DYNAMIC); // newly_hidden_layer
  //wsc_active->ws_command_swap_layer_numbers(LAYER_DRAWING_DYNAMIC,
  //					    LAYER_CURRENT_DYNAMIC);
  //wsc_active->ws_command_clear_layer(LAYER_DRAWING_DYNAMIC);

  wsc_active->ws_command_move_layer_to_layer(LAYER_DRAWING_DYNAMIC, LAYER_CURRENT_DYNAMIC);

  //printf("page_flip\n");
}


void web_display::draw_ephemeral_alert(const char* text, int text_height, color c, color bg)
{
  set_text_size(text_height);
  
  int twidth  = calculate_text_width(text);
  int theight = calculate_text_height(text);

  set_current_layer( LAYER_DRAWING_DYNAMIC );

  int corner1_x = width/2  - twidth  - 60;
  int corner1_y = height/2 - theight - 60;
  int corner2_x = width/2  + twidth  + 60;
  int corner2_y = height/2 + theight + 60;
  
  wsc_active->ws_command_rectangle( RGB_to_RGBA(bg), corner1_x, corner1_y, corner2_x, corner2_y);

  wsc_active->ws_command_line(RGB_to_RGBA(c), 1.0, corner1_x, corner1_y, corner2_x, corner1_y);
  wsc_active->ws_command_line(RGB_to_RGBA(c), 1.0, corner1_x, corner1_y, corner1_x, corner2_y);
  wsc_active->ws_command_line(RGB_to_RGBA(c), 1.0, corner2_x, corner2_y, corner1_x, corner2_y);
  wsc_active->ws_command_line(RGB_to_RGBA(c), 1.0, corner2_x, corner2_y, corner2_x, corner1_y);

  wsc_active->ws_command_text(RGB_to_RGBA(c), DRAW_TEXT_X_CENTER|DRAW_TEXT_Y_CENTER, width/2, height/2, text);

  page_flip();
}



//
// Setting the parent to null is OK, because no reference to the parent should every come out
// of a web_display. All the functions that reference a parent are overridden.
//
web_display::web_display(int num_panes)
  : display(200, 100, num_panes)
{
  wsc_active = 0;
  last_text_size = 0;
  last_wsc_active_ID = -1;
  
  current_layer = LAYER_BAD; //LAYER_DRAWING_DYNAMIC;
  mouse_buttons_pressed = 0;
  
  trash_events_flag  = false;
  draw_to_static     = 0;

  gettimeofday(&last_event_time, 0);
  last_beep_time = last_event_time;

  wif = new web_interface(80);

  touch_state = MOTION_STATE_NONE;
  
  resize(0,0,100,100);
}


web_display::~web_display()
{
}



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;
}


static bool motion_exceeds_limit(my_event& me1, my_event& me2, int limit)
{
  for(int i=0; i<me1.num_touches; i++)
    {
      if(abs(me1.c[i].x-me2.c[i].x)>limit)
	return true;
      if(abs(me1.c[i].y-me2.c[i].y)>limit)
	return true;
    }
  
  return false;
}


void web_display::get_and_process_events()
{
  web_socket_connection* old_wsc_active = wsc_active;
  long long t_us = get_time_in_us();
 
  for(;;)
    {
      //
      // Remove all packets from inactive web pages
      //
      for(ListElement<web_socket_connection> * le = wif->connections.last(); le; le = le->previous())
	{
	  web_socket_connection* wsc = le->data();
	  
	  if(wsc!=wsc_active)
	    {
	      bool have_event = wsc->event_is_on_event_queue();
	      if(have_event)
		{
		  //printf("Got an event on WebSocketConnection with ID %d\n", wsc->ID);
		  wsc->event_queue_out_position = (wsc->event_queue_out_position == WS_EVENT_QUEUE_NUM_ITEMS-1) ? 0 : wsc->event_queue_out_position + 1;
		  
		  gettimeofday(&last_event_time, 0);
		  resize(0, 0, wsc->width, wsc->height);
		  wsc_active = wsc;
		}
	    }
	}

      if(!wsc_active)
	break;

      if(wsc_active!=old_wsc_active)
	{
	  for(ListElement<web_socket_connection> * le = wif->connections.last(); le; le = le->previous())
	    {
	      web_socket_connection* wsc = le->data();
	      
	      if(wsc!=wsc_active)
		set_up_inactive_page(wsc);
	    }
	}


      if(touch_state == MOTION_STATE_INIT && t_us - last_touch_me.time_in_us > wait_for_fingers_us)
	{
	  touch_state = MOTION_STATE_HOLDING;
	  last_touch_me.count = 0;
	  last_touch_me.type = EVENT_TOUCH;

#ifdef DEBUG_TOUCH
	  printf("                                           Sending TOUCH event for INIT->HOLD after %lld us: ", t_us - last_touch_me.time_in_us);
	  ::print_event(last_touch_me);
#endif
	  
	  bool handled = handle_event(last_touch_me);

	  if(!handled)
	    {
	      draw_hotspots = true;
	      gettimeofday(&unhandled_button_press_event_time, 0);
	      entry_rejected_beep();
	    }
	}
      
      if(touch_state==MOTION_STATE_HOLDING &&  t_us - last_touch_me.time_in_us > (100000*(last_touch_me.count+1)+wait_for_fingers_us))
	{
	  last_touch_me.count++;
	  last_touch_me.type = EVENT_HOLD;

#ifdef DEBUG_TOUCH
	  printf("                                           Generating a HOLD event: ");
	  ::print_event(last_touch_me);
#endif
	  
	  handle_event(last_touch_me);
	}

      //
      // Some browsers aren't reliably sending touchend events.  Set a timeout here that if nothing is happening for 3 seconds
      // we revert to MOTION_STATE_NONE.
      //
      if(touch_state != MOTION_STATE_NONE && t_us - last_touch_me.time_in_us > 3000000)
	{
	  //printf("Timeout receiving TOUCHEND.  Assuming a buggy browser and clearing touches.\n");
	  touch_state = MOTION_STATE_NONE;
	}
      
      bool have_event = wsc_active->event_is_on_event_queue();
  
      if(!have_event)
	break;

      gettimeofday(&last_event_time, 0);

      ws_event_packet*  packet        = (ws_event_packet*)wsc_active->event_queue[wsc_active->event_queue_out_position];

      wsc_active->event_queue_out_position = (wsc_active->event_queue_out_position == WS_EVENT_QUEUE_NUM_ITEMS-1) ? 0 : wsc_active->event_queue_out_position + 1;

      //if(me.type == EVENT_KEY_PRESS)
      //{
      //  if(me.key_code==KEY_ESC)
      //    {
      //      printf("ESC key pressed, exiting.\n");
      //      exit(0);
      //    }
      //
      //  printf("Key press:  keycode %d, ascii %d ('%c')\n", me.key_code, me.key_ascii, me.key_ascii);
      //
      //  handle_event(me);
      //}
      //

      //printf("got an event of type %d\n", packet->type);

      
      if(mouse_buttons_pressed==0 && trash_events_flag)
	{
	  trash_events_flag = false;
	  //printf("no buttons pressed. trashed no more.\n");
	}

      if(trash_events_flag)
	{
	  if(packet->type==WS_EVENT_BUTTON_UP)
	    {
	      auto event = (ws_event_button_up*)packet;
	      if(event->button==0)
		mouse_buttons_pressed &= ~MOUSE_BUTTON_LEFT;
	      else if (event->button==1)
		mouse_buttons_pressed &= ~MOUSE_BUTTON_MIDDLE;
	      else if (event->button==2)
		mouse_buttons_pressed &= ~MOUSE_BUTTON_RIGHT;
	    }
	  else if(packet->type==WS_EVENT_BUTTON_DOWN)
	    {
	      auto event = (ws_event_button_down*)packet;

	      if(event->button==0)
		mouse_buttons_pressed |= MOUSE_BUTTON_LEFT;
	      else if (event->button==1)
		mouse_buttons_pressed |= MOUSE_BUTTON_MIDDLE;
	      else if (event->button==2)
		mouse_buttons_pressed |= MOUSE_BUTTON_RIGHT;
	    }
	  //printf("event of type %d was trashed.\n", packet->type);
	}
      else
	{
	  my_event me;
	  bool valid = false;

	  switch(packet->type)
	    {
	    case WS_EVENT_MOUSE_MOVE:
	      {
		auto event = (ws_event_mouse_move*)packet;

		//printf("Mouse move event with mouse_buttons_pressed=%d, x=%d, y=%d\n", mouse_buttons_pressed, event->x, event->y);

		if(!mouse_buttons_pressed)
		  break;
		me.type = EVENT_MOVE;
		me.c[0].x = event->x;
		me.c[0].y = event->y;
		me.source_mouse = true;
		me.count = 0;
		me.mouse_buttons_pressed = mouse_buttons_pressed;
		valid = true;
		break;
	      }
	    case WS_EVENT_BUTTON_DOWN:
	      {
		auto event = (ws_event_button_down*)packet;

		if(event->button==0)
		  mouse_buttons_pressed |= MOUSE_BUTTON_LEFT;
		else if (event->button==1)
		  mouse_buttons_pressed |= MOUSE_BUTTON_MIDDLE;
		else if (event->button==2)
		  mouse_buttons_pressed |= MOUSE_BUTTON_RIGHT;

		//printf("Button Down event with button=%d, x=%d, y=%d: mouse_buttons_pressed=%d\n", event->button, event->x, event->y, mouse_buttons_pressed);

		me.type = EVENT_TOUCH;
		me.c[0].x = event->x;
		me.c[0].y = event->y;
		me.num_touches = 1;
		me.source_mouse = true;
		me.count = 0;
		me.mouse_buttons_pressed = mouse_buttons_pressed;
		valid = true;
		break;
	      }
	    case WS_EVENT_BUTTON_UP:
	      {
		auto event = (ws_event_button_up*)packet;

		//printf("Button Up event with button=%d, x=%d, y=%d: mouse_buttons_pressed was %d now ", event->button, event->x, event->y, mouse_buttons_pressed);

		me.type = EVENT_RELEASE;
		me.c[0].x = event->x;
		me.c[0].y = event->y;
		me.source_mouse = true;
		me.count = 0;
		me.mouse_buttons_pressed = mouse_buttons_pressed;
		valid = true;

		if(event->button==0)
		  mouse_buttons_pressed &= ~MOUSE_BUTTON_LEFT;
		else if (event->button==1)
		  mouse_buttons_pressed &= ~MOUSE_BUTTON_MIDDLE;
		else if (event->button==2)
		  mouse_buttons_pressed &= ~MOUSE_BUTTON_RIGHT;

		//printf("%d\n", mouse_buttons_pressed);

		break;
	      }
	    case WS_EVENT_WHEEL:
	      {
		auto event = (ws_event_wheel*)packet;

		//printf("Wheel event with steps=%d, x=%d, y=%d\n", event->steps, event->x, event->y);
		
		me.type  = EVENT_WHEEL;
		me.count = event->steps;
		me.c[0].x = event->x;
		me.c[0].y = event->y;
		me.source_mouse = true;
		me.mouse_buttons_pressed = mouse_buttons_pressed;
		valid = true;
		break;
	      }
	    case WS_EVENT_RESIZE:
	      {
		auto event = (ws_event_resize*)packet;

		//printf("Resize event with width=%d, height=%d\n", event->width, event->height);

		resize(0, 0, event->width, event->height);
		valid = false;
		break;
	      }
	    case WS_EVENT_TOUCHSTART:
	      {
#ifdef DEBUG_TOUCH
		print_touch_event(packet, touch_state, t_us);
#endif
		if(touch_state != MOTION_STATE_NONE && touch_state != MOTION_STATE_INIT  && touch_state != MOTION_STATE_ERROR)
		  {
		    //printf("Unexpected touch put us in state ERROR.\n");
		    touch_state = MOTION_STATE_ERROR;
		    me = last_touch_me;
		    me.type = EVENT_RELEASE;
		    me.time_in_us = t_us;
		    valid = true;
		    break;
		  }

		if(touch_state != MOTION_STATE_ERROR)
		  {
		    auto event = (ws_event_touch*)packet;
		    me.type = EVENT_TOUCH;
		    me.num_touches = (event->num_touches<5) ? event->num_touches : 5;
		    for(int i=0; i<me.num_touches; i++)
		      {
			me.c[i].x = event->touches[i].x;
			me.c[i].y = event->touches[i].y;
		      }
		    me.source_mouse = false;
		    me.count = 0;
		    me.time_in_us = t_us;
		    first_touch_me = me;
		    last_touch_me = me;
		    touch_state = MOTION_STATE_INIT;
		    //printf("New touch put us in state INIT.\n");
		    valid = false;  // Don't send this yet;  It is only sent after a wait to see if other fingers show up.
		  }
		break;
	      }
	    case WS_EVENT_TOUCHMOVE:
	      {
#ifdef DEBUG_TOUCH
		print_touch_event(packet, touch_state, t_us);
#endif
		if(touch_state == MOTION_STATE_INIT || touch_state == MOTION_STATE_HOLDING || touch_state == MOTION_STATE_MOVING)
		  {
		    auto event = (ws_event_touch*)packet;
		    me.type = EVENT_MOVE;
		    me.num_touches = (event->num_touches<5) ? event->num_touches : 5;
		    for(int i=0; i<me.num_touches; i++)
		      {
			me.c[i].x = event->touches[i].x;
			me.c[i].y = event->touches[i].y;
		      }
		    me.source_mouse = false;
		    me.count = 0;
		    me.time_in_us = t_us;
		    if(touch_state == MOTION_STATE_HOLDING && motion_exceeds_limit(me, last_touch_me, motion_to_break_hold))
		      {
			//printf("Motion popped us out of HOLD into MOVE.\n");
			touch_state = MOTION_STATE_MOVING;
		      }

		    last_touch_me = me;

		    if(touch_state==MOTION_STATE_MOVING)
		      {
			valid = true;
		      }

		    // Don't send any move events on.  They cause web displays to be overwhelmed, and then
		    // not send TOUCHEND events.
		    valid = false;
		  }
		break;
	      }
	    case WS_EVENT_TOUCHEND:
	    case WS_EVENT_TOUCHCANCEL:
	      {
#ifdef DEBUG_TOUCH
		print_touch_event(packet, touch_state, t_us);
#endif
		if(touch_state == MOTION_STATE_INIT)
		  {
		    // We just got an almost instantaneous touch.  Send a touch, a move, and a release.
		    me = first_touch_me;
		    me.type = EVENT_TOUCH;

#ifdef DEBUG_TOUCH
		    printf("                                           Sending event from INIT: ");
		    ::print_event(me);
#endif
		    
		    bool handled = handle_event(me);
		    if(!handled)
		      {
			draw_hotspots = true;
			gettimeofday(&unhandled_button_press_event_time, 0);
			entry_rejected_beep();
			valid = false;
		      }
		    else
		      {
			me = last_touch_me;
			me.type = EVENT_MOVE;

#ifdef DEBUG_TOUCH
			printf("                                           Sending event from INIT: ");
			::print_event(me);
#endif
			
			handled = handle_event(me);
		      
			if(!handled)
			  {
			    draw_hotspots = true;
			    gettimeofday(&unhandled_button_press_event_time, 0);
			    entry_rejected_beep();
			    valid = false;
			  }
			else
			  {
			    me = last_touch_me;
			    me.time_in_us = t_us;
			    me.type = EVENT_RELEASE;
			    valid = true;
			  }
		      }
		  }
		else if(touch_state == MOTION_STATE_HOLDING || touch_state == MOTION_STATE_MOVING)
		  {
		    me = last_touch_me;
		    me.type = EVENT_MOVE;

#ifdef DEBUG_TOUCH
		    printf("                                           Sending event from HOLDING_OR_MOVING: ");
		    ::print_event(me);
#endif
		    
		    bool handled = handle_event(me);
		    
		    if(!handled)
		      {
			draw_hotspots = true;
			gettimeofday(&unhandled_button_press_event_time, 0);
			entry_rejected_beep();
			valid = false;
		      }
		    else
		      {
			me = last_touch_me;
			me.time_in_us = t_us;
			me.type = EVENT_RELEASE;
			valid = true;
		      }
		  }

		auto event = (ws_event_touch*)packet;
		if(event->num_touches==0)
		  {
		    touch_state = MOTION_STATE_NONE;
		    //printf("Changed to state NONE from touchend.\n");
		  }
		else
		  {
		    touch_state = MOTION_STATE_ERROR; // Not really an error, but this state flushes all input until we get a touchend taking us to zero touches.
		    //printf("Changed to state ERROR from touchend.\n");		    
		  }
		break;
	      }
	    default:
	      printf("got an unhandled packet type %d (0x%08x)\n", packet->type, packet->type);
	      break;
	    }
	      // Events to work on generating:
	      // EVENT_HOLD  // Touch one or more positions and don't move.  How long is in hold_count

	      //
	      // Keyboard events.  hold_count is also used for a key hold.
	      //
	      //EVENT_KEY_PRESS,
	      //EVENT_KEY_HOLD,
	      //EVENT_KEY_RELEASE,

	  
	  if(valid)
	    {
#ifdef DEBUG_TOUCH
	      printf("                                           Sending event: ");
	      ::print_event(me);
#endif
	      
	      bool handled = handle_event(me);
	      
	      if(!handled && me.type==EVENT_TOUCH)
		{
		  draw_hotspots = true;
		  gettimeofday(&unhandled_button_press_event_time, 0);
		  entry_rejected_beep();
		}
	    }
	}
    }


  if(draw_hotspots)
    {
      struct timeval t;
      gettimeofday(&t, 0);

      long long t0_us = unhandled_button_press_event_time.tv_sec*1000000ll + unhandled_button_press_event_time.tv_usec;
      long long t1_us = t.tv_sec*1000000ll + t.tv_usec;

      long long elapsed_us = t1_us - t0_us;

      //printf("Draw hotspots elapsed time is %lld\n", elapsed_us);
      
      if(elapsed_us > 1000000ll)
	draw_hotspots = false;
    }


}



