
// 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 "numeric_entry.hh"
#include "global_beep.hh"
#include "draw_text.hh"
#include "event_types.hh"
#include "send_events.hh"
#include "hotspot.hh"
#include "language_support.hh"

#include <string.h>

bool numeric_entry_button_push(button*                   sender,
			       selection_changed_event   event_data,
			       numeric_entry*            receiver,
			       char                      receiver_data,
			       int&                      event_return)
{
  receiver->mark_dirty();
  
  bool     handled = receiver->got_char(receiver_data);  // receiver may be deleted in this call.  Don't do anything with it afterwards.

  return handled;
}


bool numeric_entry::add_char(char c)
{
  display* disp = get_display();

  if(entry[0]=='-' && current_entry_point >= MAX_ENTRY)
    {
      disp->entry_rejected_beep();
      return false;
    }
  else if(entry[0]!='-' && current_entry_point >= MAX_ENTRY-1)
    {
      disp->entry_rejected_beep();
      return false;
    }
  else
    {
      entry[current_entry_point++] = c;
      entry[current_entry_point]   = 0;
      disp->touch_recognized_beep();
      return true;
    }
}

bool numeric_entry::got_char(char c)
{
  display* disp = get_display();

  if( (c>='a' && c<='d') || (c=='D' && current_entry_point==0) )
    {
      int end_number = c - 'a';
      bool accepted = rtn(rtn_data, entry, end_number);

      if(accepted)
	{
	  disp->entry_accepted_beep();
	  d->end_inner_event_loop();
	  delete this;
	  return true;
	}
      else
	{
	  disp->entry_rejected_beep();
	  return false;
	}
    }

  if(c=='.')
    {
      if(got_decimal_point)
	{
	  return false;
	}
      
      bool added = add_char(c);
      got_decimal_point = added;
      return added;
    }

  if(c=='-' || c==',')
    {
      if(entry[0]=='-')
	{
	  for(int i=0; i<current_entry_point; i++)
	    entry[i] = entry[i+1];

	  current_entry_point--;
	  entry[current_entry_point] = 0;
	  disp->touch_recognized_beep();
	  return true;
	}
      else
	{
	  for(int i=current_entry_point; i>0; i--)
	    entry[i] = entry[i-1];

	  current_entry_point++;
	  entry[current_entry_point] = 0;
	  entry[0] = '-';
	  disp->touch_recognized_beep();
	  return true;
	}
    }

  if(c=='D')
    {
      current_entry_point--;

      if(entry[current_entry_point]=='.')
	got_decimal_point = false;
      
      entry[current_entry_point] = 0;
      disp->touch_recognized_beep();
      return true;
    }
  
  if(c>='0' && c<='9')
    {
      bool added = add_char(c);
      return added;
    }
  
  disp->entry_rejected_beep();
  return false;
}


numeric_entry::numeric_entry(display*         d,
			     const char32_t*  title,
			     const char32_t*  end_text_0,
			     const char32_t*  end_text_1,
			     const char32_t*  end_text_2,
			     const char32_t*  end_text_3,
			     bool             integer_only,
			     bool             positive_only,
			     return_function  rtn,
			     void*            rtn_data,
			     int              default_return_number,
			     double           default_value)
  : multiwindow(BLACK)
{
  this->d = d;

  this->default_return_number  = default_return_number;
  this->default_value          = default_value;

  window_subject_to_layout = false;

  this->rtn      = rtn;
  this->rtn_data = rtn_data;
  
  BG_COLOR             = GREY4;
  BUTTON_COLOR         = GREY6;
  TEXT_COLOR           = { WHITE  };
  TEXT_COLOR_FLASH     = { RED  };
  ACTIONTEXTCOLOR      = { GREEN7 };
  TITLE_COLOR          = WHITE;

  text_font_height     = 50;    // Overridden in draw_dirty
  font_height          = 80;
  circle_width         = 30;

  this->title = title;
  this->end_text_0 = end_text_0;
  this->end_text_1 = end_text_1;
  this->end_text_2 = end_text_2;
  this->end_text_3 = end_text_3;

  this->integer_only   = integer_only;
  this->positive_only  = positive_only;

  got_decimal_point    = false;
  entry[0] = 0;
  current_entry_point  = 0;

  int button_circle_width_percent = 10;
  
  for(int i=0; i<10; i++)
    {
      char txt[2];
      sprintf(txt, "%d", i);
      b_num[i] = new button(txt, 5, 5, button_circle_width_percent, TEXT_COLOR, BUTTON_COLOR, TEXT_COLOR_FLASH, BLUE);
      b_num[i]->silent = true;
    }

  if(integer_only)
    {
      b_p = 0;
    }
  else
    {
      const char* period = use_comma_for_decimal_point ? "," : ".";
      b_p = new button(period, 5, 5, button_circle_width_percent, TEXT_COLOR, BUTTON_COLOR, TEXT_COLOR_FLASH, BLUE);
      b_p->silent = true;
    }

  //
  // Always have the +/-, since sizing is affected otherwise.  But if it's positive_only, put it off-screen
  //
  b_pm = new button("+/-", 5, 5, button_circle_width_percent, TEXT_COLOR, BUTTON_COLOR, TEXT_COLOR_FLASH, BLUE);
  b_pm->silent = true;

  dt_colors b_delete_colors = { RED };

  // Unicode delete symbol
  b_delete = new button(U"\x232B", 5, 5, button_circle_width_percent, b_delete_colors, BUTTON_COLOR, TEXT_COLOR_FLASH, BLUE);
  b_delete->silent = true;

  if(end_text_0)
    {
      b_end[0] = new button(end_text_0, 5, 5, button_circle_width_percent, ACTIONTEXTCOLOR, BUTTON_COLOR, TEXT_COLOR_FLASH, BLUE);
      b_end[0]->silent = true;
    }
  else
    {
      b_end[0] = 0;
    }
  
  if(end_text_1)
    {
      b_end[1] = new button(end_text_1, 5, 5, button_circle_width_percent, ACTIONTEXTCOLOR, BUTTON_COLOR, TEXT_COLOR_FLASH, BLUE);
      b_end[1]->silent = true;
    }
  else
    {
      b_end[1] = 0;
    }
  
  if(end_text_2)
    {
      b_end[2] = new button(end_text_2, 5, 5, button_circle_width_percent, ACTIONTEXTCOLOR, BUTTON_COLOR, TEXT_COLOR_FLASH, BLUE);
      b_end[2]->silent = true;
    }
  else
    {
      b_end[2] = 0;
    }
  
  if(end_text_3)
    {
      b_end[3] = new button(end_text_3, 5, 5, button_circle_width_percent, ACTIONTEXTCOLOR, BUTTON_COLOR, TEXT_COLOR_FLASH, BLUE);
      b_end[3]->silent = true;
    }
  else
    {
      b_end[3] = 0;
    }
  
  //
  // Add subcomponents to multiwindow
  //
  add(b_delete);

  for(int n=0; n<10; n++)
    add(b_num[n]);

  if(b_pm)
    add(b_pm);

  if(b_p)
    add(b_p);

  for(int i=0; i<4; i++)
    if(b_end[i])
      add(b_end[i]);

  

  //
  // Make connectors
  //
  if(b_p)
    c_p        = new connection<button, selection_changed_event, numeric_entry, char, int>(this, '.', numeric_entry_button_push);

  if(b_pm)
    c_pm      = new connection<button, selection_changed_event, numeric_entry, char, int>(this, '-', numeric_entry_button_push);

  c_delete    = new connection<button, selection_changed_event, numeric_entry, char, int>(this, 'D', numeric_entry_button_push);

  for(int i=0; i<10; i++)
    c_num[i]  = new connection<button, selection_changed_event, numeric_entry, char, int>(this, '0'+i, numeric_entry_button_push);

  for(int i=0; i<4; i++)
    if(b_end[i])
      c_end[i]  = new connection<button, selection_changed_event, numeric_entry, char, int>(this, 'a'+i, numeric_entry_button_push);


  //
  // Connect connectors
  //

  if(b_p)
    b_p       -> add_receiver(c_p);

  if(b_pm)
    b_pm      -> add_receiver(c_pm);

  b_delete    -> add_receiver(c_delete);

  for(int i=0; i<10; i++)
    b_num[i]  -> add_receiver(c_num[i]);

  for(int i=0; i<4; i++)
    if(b_end[i])
      b_end[i]  -> add_receiver(c_end[i]);

  d->add(this, true);

  //printf("Calling numeric_entry::layout()\n");
  layout();
}


numeric_entry::~numeric_entry()
{
  // Delete the connections.
  
  if(b_p)
    delete c_p;

  if(b_pm)
    delete c_pm;

  delete c_delete;

  for(int i=0; i<10; i++)
    delete c_num[i];

  for(int i=0; i<4; i++)
    if(b_end[i])
      delete c_end[i];

  // Deleting everything else is handled by the multiwindow subclass

  release_events();
  d->mark_layout_dirty();
}


  
void numeric_entry::draw_dynamic()
{
  multiwindow::draw_dynamic();
}
  

bool numeric_entry::handle_event(my_event& me)
{
  bool handled = multiwindow::handle_event(me);

  if(!handled  && (me.type == EVENT_TOUCH))
    {
      display* disp = get_display();
      disp->entry_rejected_beep();
      
      for(int i=0; i<10; i++)
	b_num[i]   -> highlight_count = 100;

      for(int i=0; i<4; i++)
	if(b_end[i])
	  b_end[i] -> highlight_count = 100;

      if(b_p && !got_decimal_point)
	b_p        -> highlight_count = 100;

      if(b_pm)
	b_pm       -> highlight_count = 100;
      
      b_delete     -> highlight_count = 100;
    }
  
  return true;
}




void numeric_entry::layout()
{
  //printf("numeric_entry::layout() called.\n");
  
  release_events();
  claim_events();

  float designed_height = 1080.0;
  float designed_width  = 1920.0;
  float ratio_x = d->width / designed_width;
  float ratio_y = d->height / designed_height;
  float ratio = (ratio_x<ratio_y) ? ratio_x : ratio_y;

  //
  // Do the layout here
  //
  title_margin   = 20 * ratio;
  black_margin   = 20 * ratio;

  top_margin    = 20 * ratio + black_margin;
  left_margin   = 20 * ratio + black_margin;
  right_margin  = 20 * ratio + black_margin;
  bottom_margin = 20 * ratio + black_margin;

  button_margin = 10 * ratio;
  right_column_margin = 20 * ratio;

  text_font_height     =  50 * ratio;
  font_height          =  80 * ratio;
  circle_width         =  30 * ratio;
  button_height        =  90 * ratio;

  //  int button_circle_width = 20 * ratio;
  
  //
  // Layout:        Title Here
  //               TTTTTTTT Del
  //               1  2  3  end0
  //               4  5  6  end1
  //               7  8  9  end2
  //              +/- 0  .  end3
  //
  // Title calls out what the number being entered
  // is for.  The TTTTT area is a text display of
  // the current number, in a box.  The end0, end1,
  // etc. are different enter keys.  For example
  // if this was for entering frequency then they
  // might be blank, kHz, MHz, GHz.
  //
  //

  set_text_size(font_height);
  
  //
  // x-layout
  //
  if(b_pm)
    button_width = b_pm->calculate_preferred_width(button_height);
  else
    button_width = b_num[0]->calculate_preferred_width(button_height);
  
  del_width = b_delete->calculate_preferred_width(button_height);

  right_column_width = del_width;
  for(int i=0; i<4; i++)
    {
      if(b_end[i])
	{
	  int end_width = b_end[i]->calculate_preferred_width(button_height);

	  right_column_width = max(right_column_width, end_width);
	}
    }


  text_width_full    = 3 * button_width + 2 * button_margin;
  text_margin        = 5;
  text_width         = text_width_full - 2*text_margin;

  //
  // y-layout
  //
  title_height       = font_height;

  text_height        = font_height;
  text_height_full   = text_height + 2*text_margin;
  below_text_margin  = text_margin + button_margin;
  
  width                = left_margin + text_width_full + right_column_margin + right_column_width + right_margin;
  height               = top_margin + title_height + title_margin + text_height_full + below_text_margin + 4 * button_height + 3 * button_margin + bottom_margin;
  offset.x             = (parent->width  - width)/2;
  offset.y             = (parent->height - height)/2;

  //
  // Do resizing
  //
  int right_column_offset = left_margin + text_width_full + right_column_margin;
  int below_text_offset   = top_margin + title_height + title_margin + text_height_full + below_text_margin;
  int button_width_full   = button_width + button_margin;
  int button_height_full  = button_height + button_margin;

  b_delete->resize(right_column_offset,                 top_margin + title_height + title_margin,     right_column_width,   button_height);

  b_num[1]->resize(left_margin,                         below_text_offset,                            button_width,         button_height);
  b_num[2]->resize(left_margin +   button_width_full,   below_text_offset,                            button_width,         button_height);
  b_num[3]->resize(left_margin + 2*button_width_full,   below_text_offset,                            button_width,         button_height);

  if(b_end[0])
    b_end[0]->resize(right_column_offset,               below_text_offset,                            right_column_width,         button_height);


  b_num[4]->resize(left_margin,                         below_text_offset +   button_height_full,     button_width,         button_height);
  b_num[5]->resize(left_margin +   button_width_full,   below_text_offset +   button_height_full,     button_width,         button_height);
  b_num[6]->resize(left_margin + 2*button_width_full,   below_text_offset +   button_height_full,     button_width,         button_height);

  if(b_end[1])
    b_end[1]->resize(right_column_offset,               below_text_offset +   button_height_full,     right_column_width,         button_height);

  
  b_num[7]->resize(left_margin,                         below_text_offset + 2*button_height_full,     button_width,         button_height);
  b_num[8]->resize(left_margin +   button_width_full,   below_text_offset + 2*button_height_full,     button_width,         button_height);
  b_num[9]->resize(left_margin + 2*button_width_full,   below_text_offset + 2*button_height_full,     button_width,         button_height);

  if(b_end[2])
    b_end[2]->resize(right_column_offset,               below_text_offset + 2*button_height_full,     right_column_width,         button_height);


  if(b_pm)
    {
      if(positive_only)
	b_pm  ->resize(d->width,                                                           d->height,     button_width,         button_height);
      else
	b_pm  ->resize(left_margin,                         below_text_offset + 3*button_height_full,     button_width,         button_height);
    }
  
  b_num[0]->resize(left_margin +   button_width_full,   below_text_offset + 3*button_height_full,     button_width,         button_height);

  if(b_p)
    b_p   ->resize(left_margin + 2*button_width_full,   below_text_offset + 3*button_height_full,     button_width,         button_height);

  if(b_end[3])
    b_end[3]->resize(right_column_offset,               below_text_offset + 3*button_height_full,     right_column_width,         button_height);


  //
  // Do layout
  //
  b_delete->layout();

  for(int n=0; n<10; n++)
    b_num[n]->layout();

  if(b_pm)
    b_pm->layout();

  if(b_p)
    b_p->layout();

  for(int i=0; i<4; i++)
    if(b_end[i])
      b_end[i]->layout();

  
  draw_hotspots = false;
  layout_dirty = false;
  dirty = true;
}

void numeric_entry::draw_dirty()
{
  //  printf("numeric_entry::draw_dirty() called.  width=%d, height=%d, circle_width=%d\n", width, height, circle_width);
  
  set_text_size(font_height);

  if(entry[0]==0)
    {
      for(int i=0; i<4; i++)
	if(b_end[i])
	  {
	    if(i==default_return_number)
	      b_end[i]->enable();
	    else
	      b_end[i]->disable();
	  }
      
      b_delete->disable();
    }
  else
    {
      for(int i=0; i<4; i++)
	if(b_end[i])
	  b_end[i]->enable();

      b_delete->enable();
    }

  draw_rect_with_curved_edges(GREY2, 0, 0, width, height, circle_width); 

  //  clear(BG_COLOR);
  draw_rect_with_curved_edges(BG_COLOR, black_margin, black_margin, width-2*black_margin, height-2*black_margin, circle_width); 

  draw_text(title, TITLE_COLOR, width/2, top_margin + title_height, DRAW_TEXT_X_CENTER | DRAW_TEXT_Y_BOTTOM | DRAW_TEXT_ROTATE_0);
  
  //
  // Draw entry box
  //
  draw_rect_with_curved_edges(BLACK, left_margin, top_margin + title_height + title_margin, text_width_full, text_height_full, circle_width); 

  set_text_size(text_font_height);

  if(entry[0]==0)
    {
      char txt[200];
      float2text(default_value, txt);
      draw_text(txt, ACTIONTEXTCOLOR.c[0], left_margin + text_margin + text_width, top_margin + title_height + title_margin + text_height_full/2, DRAW_TEXT_X_RIGHT | DRAW_TEXT_Y_CENTER | DRAW_TEXT_ROTATE_0);
    }
  else
    {
      char* txt = entry;
      if(use_comma_for_decimal_point)
	{
	  char text2[200];
	  txt = text2;
	  
	  for(int i=0;;i++)
	    {
	      if(entry[i]=='.')
		text2[i] = ',';
	      else
		text2[i] = entry[i];
	      
	      if(!entry[i])
		break;
	    }
	}
      

      draw_multicolored_text(txt, 0, TEXT_COLOR, left_margin + text_margin + text_width, top_margin + title_height + title_margin + text_height_full/2, DRAW_TEXT_X_RIGHT | DRAW_TEXT_Y_CENTER | DRAW_TEXT_ROTATE_0);
    }

  for(ListElement<window>* le=subwindows->first(); le; le=le->next())
    {
      window* w = le->data();
      if(w->layout_dirty)
	w->layout();
      
      w->draw_dirty();
    }
}

