
// 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 "multiselect.hh"
#include "displays.hh"

extern bool draw_hotspots;


void multiselect::set_title(multilingua& txt)
{
  title = txt;
}


void multiselect::add(multilingua& item_name, bool start_selected, bool disabled)
{
  if(num_items==MAX_ITEMS)
    {
      printf("Error: adding more items to multiselect than it will hold.\n");
      abort();
    }
  this->item_name[num_items] = item_name;
  this->selected[num_items] = (!disabled) && start_selected;
  this->disabled[num_items] = disabled;
  this->textcolor[num_items]                = TEXTCOLOR;
  this->selectedtextcolor[num_items]        = SELECTEDTEXTCOLOR;
  this->selected_highlight_color[num_items] = SELECTED_HIGHLIGHT_COLOR;
  num_items++;
}


multiselect::multiselect(multilingua& title, int max_selected, color bg)
  : window(bg)
{
  this->max_selected = max_selected;
  num_items = 0;
  item_font_height = 16;
  change_function = 0;
  change_function_data = 0;

  this->title = title;
  
  highlight_count = 0;

  BOXCOLOR                  = GREY2;
  GROUPCOLOR                = GREY4;
  SELECTED_HIGHLIGHT_COLOR  = GREY6;
  TEXTCOLOR                 = GREY9;
  SELECTEDTEXTCOLOR         = WHITE;
  DISABLEDTEXTCOLOR         = GREY6;
  TITLECOLOR                = WHITE;
}




void multiselect::set_text_color(int item, color c)
{
  if(item<0 || item>=num_items)
    {
      printf("ERROR: setting text color of nonexistent item %d in multiselect\n", item);
      abort();
    }

  this->textcolor[item] = c;
}


void multiselect::set_selected_text_color(int item, color c)
{
  if(item<0 || item>=num_items)
    {
      printf("ERROR: setting selected text color of nonexistent item %d in multiselect\n", item);
      abort();
    }

  this->selectedtextcolor[item] = c;
}


void multiselect::set_selected_highlight_color(int item, color c)
{
  if(item<0 || item>=num_items)
    {
      printf("ERROR: setting color of nonexistent item in multiselect\n");
      abort();
    }

  this->selected_highlight_color[item] = c;
}


void multiselect::draw_item(int num)
{
  int item_select_top_offset  = below_title_offset + top_margin + num * item_height;

  //printf("item %d \"%s\" drawn with top left (%d,%d)\n", num, item_name[num], item_select_top_offset, item_select_left_offset);
  
  if(selected[num])
    {
      draw_rect_with_curved_edges(selected_highlight_color[num],
				  item_select_left_offset,
				  item_select_top_offset,
				  item_select_width,
				  item_select_height,
				  circle_width);
    }
  else
    {
      clear_rect(GROUPCOLOR,
		 point(item_select_left_offset, item_select_top_offset-1),
		 point(item_select_left_offset+item_select_width, item_select_top_offset+item_select_height-1));
    }
  
  int item_y_center_offset    = item_select_top_offset + item_select_height/2;

  if(selected[num])
    draw_text(item_name[num], selectedtextcolor[num], item_left_offset+extra_text_x_margin, item_y_center_offset, DRAW_TEXT_X_LEFT|DRAW_TEXT_Y_CENTER|DRAW_TEXT_ROTATE_0);
  else if(disabled[num])
    draw_text(item_name[num], DISABLEDTEXTCOLOR,      item_left_offset+extra_text_x_margin, item_y_center_offset, DRAW_TEXT_X_LEFT|DRAW_TEXT_Y_CENTER|DRAW_TEXT_ROTATE_0);
  else
    draw_text(item_name[num], textcolor[num],         item_left_offset+extra_text_x_margin, item_y_center_offset, DRAW_TEXT_X_LEFT|DRAW_TEXT_Y_CENTER|DRAW_TEXT_ROTATE_0);
}


void multiselect::draw_dirty()
{
  if(dirty)
    {
      set_text_size(item_font_height);

      clear(bgcolor);

      draw_rect_with_curved_edges(BOXCOLOR,
				  0,
				  0,
				  width,
				  height,
				  circle_width);
      
      if(title[0])
	draw_text(title, TITLECOLOR, width/2, below_title_offset/4, DRAW_TEXT_X_CENTER|DRAW_TEXT_Y_TOP|DRAW_TEXT_ROTATE_0);

      for(int i=0; i<num_items; i++)
	draw_item(i);
      dirty = false;
    }
}



void multiselect::draw_dynamic()
{
  if(draw_hotspots || highlight_count)
    {
      bool draw_unselected = true;

      if(highlight_count)
	highlight_count--;
	  
      if(max_selected != -1)
	{
	  int num_selected = 0;
	  for(int i=0; i<num_items; i++)
	    {
	      if(selected[i])
		num_selected++;
	    }
	  if(num_selected>=max_selected)
	    {
	      draw_unselected = false;
	    }
	}


      for(int i=0; i<num_items; i++)
	{
	  int item_select_top_offset  = below_title_offset + top_margin + i * item_height;

	  if(!draw_unselected && !selected[i])
	    continue;
	    
	  point tl( item_select_left_offset,                        item_select_top_offset);
	  point tr( item_select_left_offset + item_select_width-1,  item_select_top_offset);
	  point bl( item_select_left_offset,                        item_select_top_offset+item_select_height-1);
	  point br( item_select_left_offset + item_select_width-1,  item_select_top_offset+item_select_height-1);
	  draw_line(tl, tr, GREEN);
	  draw_line(tl, bl, GREEN);
	  draw_line(tr, br, GREEN);
	  draw_line(bl, br, GREEN);
	}
    }
}


void multiselect::select(int item)
{
  if(!selected[item] && !disabled[item])
    {
      selected[item] = true;
      mark_dirty();
      int returnval = 0;
      selection_changed_event sce(item, item_name[item], true);
      send_event(this, sce, returnval);
      if(change_function)
	change_function(&sce, this, change_function_data);
    }
}

void multiselect::deselect(int item)
{
  if(selected[item])
    {
      selected[item] = false;
      mark_dirty();
      int returnval = 0;
      selection_changed_event sce(item, item_name[item], false);
      send_event(this, sce, returnval);
      if(change_function)
	change_function(&sce, this, change_function_data);
    }
}

bool multiselect::handle_event(my_event& me)
{
  if(me.type == EVENT_TOUCH)
    {  
      int xoff = 0;
      int yoff = 0;
      window* p = this;
      while(p)
	{
	  xoff += p->offset.x;
	  yoff += p->offset.y;
	  p = p->parent;
	}

      for(int item=0; item<num_items; item++)
	{
	  if(disabled[item])
	    continue;
	  
	  int item_top_offset = below_title_offset + top_margin + item * item_height;
	  
	  int xitem = me.c[0].x - xoff - item_left_offset;
	  int yitem = me.c[0].y - yoff - item_top_offset;
	  
	  if(xitem>=0 && xitem<item_width && yitem>=0 && yitem<item_height)
	    {
	      display* disp = get_display();
	      disp->touch_recognized_beep();
	      trash_events_until_release();

	      // Do this last; the action might be do delete this class, so we don't
	      // want to take actions on a deleted class.
	      if(!selected[item])
		{
		  if(max_selected != -1)
		    {
		      int num_selected = get_num_selected();
		      if(num_selected>=max_selected)
			{
			  // Reject the event -- we already have the max selections.
			  return false;
			}
		    }
		  select(item);
		}
	      else
		{
		  deselect(item);
		}
	      
	      return true;
	    }
	}
    }

  return false;
}


int multiselect::get_num_selected()
{
  int num_selected = 0;
  for(int i=0; i<num_items; i++)
    {
      if(selected[i])
	num_selected++;
    }
  return num_selected;
}

int multiselect::calculate_item_width(int item)
{
  set_text_size(item_font_height);
  return calculate_text_width(item_name[item]);
}


static int limit(int v, int minval, int maxval)
{
  if(v<minval)
    return minval;
  if(v>maxval)
    return maxval;

  return v;
}


void multiselect::calculate_width_and_height(int& calc_width, int& calc_height, int allowed_height, float ratio)
{
  this->allowed_height  = allowed_height;
  top_margin            = limit(allowed_height/ratio * 3/100, 10, 20) * ratio;
  bottom_margin         = top_margin;  
  left_margin           = top_margin;
  right_margin          = top_margin;

  below_title_margin    = top_margin/5;
  
  font_margin           = top_margin/10;
  selection_margin      = top_margin/2;

  item_area_height  = allowed_height - top_margin - bottom_margin;
  int target_item_height  = (item_area_height+selection_margin) / (num_items+1);

  int desired_height    = target_item_height - selection_margin - 2*font_margin;
  
  item_font_height      = limit(desired_height/ratio, 10, 35) * ratio;

  set_text_size(item_font_height);

  item_height = item_font_height + 2*font_margin + selection_margin;
  
  calc_width = 0;
  for(int item=0; item<num_items; item++)
    {
      int this_width = calculate_item_width(item);
      if(calc_width<this_width)
	calc_width = this_width;
    }

  extra_text_x_margin = 0;
  if(title[0])
    {
      int title_width = calculate_text_width(title);
      if(title_width>calc_width)
	{
	  extra_text_x_margin = (title_width-calc_width)/2;
	  calc_width = title_width;
	}
    }
      

  calc_width += left_margin + right_margin;
  
  if(title[0])
    below_title_offset  = item_font_height + below_title_margin;
  else
    below_title_offset  = 0;

  calc_height = below_title_offset + top_margin + item_height * num_items - selection_margin - font_margin + bottom_margin;
}


//
// The multiselect is unusual, in that we need to get information from it about how much
// room it takes, rather than telling it and making it fit.  To do this,
// calculate_width_and_height is called first.  This returns the proper size to
// resize the window with resize(x,y,w,h);
//
void multiselect::layout()
{
  float designed_height = 1080.0;
  float designed_width  = 1920.0;
  display* d = get_display();
  float ratio_x = d->width / designed_width;
  float ratio_y = d->height / designed_height;
  float ratio = (ratio_x<ratio_y) ? ratio_x : ratio_y;

  int calc_width;
  int calc_height;

  calculate_width_and_height(calc_width, calc_height, height, ratio);

  if(calc_height>height)
    {      
      printf("in multiselect::layout(), calculated height too big;  (x,y)=(%d,%d), width=%d, height=%d, allowed_height=%d, calc_width=%d, calc_height=%d\n", offset.x, offset.y, width, height, allowed_height, calc_width, calc_height);
    }
  
  width = calc_width;
  height = calc_height;
  
  item_select_height = item_height - selection_margin;

  //
  // Determine max width of items.  All items on this list of windows are actually multiselect_items.
  //

  item_width = width - left_margin - right_margin;
  
  item_left_offset        = left_margin;
  
  item_x_center_offset    = width/2;
  item_select_left_offset = left_margin - selection_margin;
  item_select_width       = width - left_margin - right_margin + 2*selection_margin;

  circle_width = width/20;
  
  layout_dirty = false;
  dirty = true;
}


multiselect::~multiselect()
{
}

