
// 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 "layout.hh"
#include "multiwindow.hh"

//
// Default is a tile layout.  Arrange windows in multiple rows/columns so that their
// aspect ratio is close to 3:2
//
void layout_plan::do_layout(int x, int y, int w, int h, int xmargin, int ymargin, List<window>& windowlist, int aspect_x, int aspect_y, bool force_aspect)
{
  //printf("In do_layout.  (%d,%d) %dx%d\n", x, y, w, h);

  int num_windows = 0;  

  for(ListElement<window>* le=windowlist.first(); le; le=le->next())
    {
      window* win = le->data();
      if(win->window_subject_to_layout)
	num_windows++;
    }

  if(num_windows==0)
    return;

  //printf("\nlaying out %d windows.\n", num_windows);
  
  int num_columns_best = 1;
  float aspect_goodness_best = 1;
  for(int num_columns=1; num_columns<=num_windows; num_columns++)
    {
      float aspect = 1e9;  // Disqualifying value
      
      int num_rows  = (num_windows+num_columns-1)/num_columns;


      //printf("trying %d rows, %d columns.\n", num_rows, num_columns);
      
      int subwidth  = (w-xmargin*(num_columns-1))/num_columns;
      int subheight = (h-ymargin*(num_rows-1))/num_rows;
      
      if(subwidth>0 && subheight>0)
	aspect  =  float(subwidth)/subheight;

      float aspect_goodness = fabs(log(aspect*aspect_y/aspect_x));  // closest to zero is best

      // Encourage layouts that actually fill the screen.
      if(num_rows*num_columns>num_windows)
	aspect_goodness += 0.5;

      //printf("num_columns=%d, subwidth=%d, subheight=%d, aspect=%f, target_aspec=%f, aspect_x=%d, aspect_y=%d, aspect_goodness=%f\n", num_columns, subwidth, subheight, aspect, float(aspect_x)/aspect_y, aspect_x, aspect_y, aspect_goodness);

      
      if(num_columns==1 || aspect_goodness < aspect_goodness_best)
	{
	  aspect_goodness_best = aspect_goodness;
	  num_columns_best     = num_columns;
	}
    }

  int num_rows_best  = (num_windows+num_columns_best-1)/num_columns_best;
  int subwidth       = (w-xmargin*(num_columns_best-1))/num_columns_best;
  int subheight      = (h-ymargin*(num_rows_best-1))/num_rows_best;

  // Have now found the best arrangement, in num_columns_best and num_rows_best

  // Find the size we want each window, that's the biggest but has a 3:2 aspect ratio.
  int window_width  = subwidth;
  int window_height = subheight;

  if(force_aspect)
    {
      if(window_width>window_height * aspect_x/aspect_y)
	{
	  // window width is too wide, reduce it to 3/2
	  window_width = window_height * aspect_x/aspect_y;
	}
      else
	{
	  // window height is too tall, reduce it to 3/2
	  window_height = window_width * aspect_y / aspect_x;
	}
    }

  int extra_xoffset = (subwidth - window_width) / 2;
  int extra_yoffset = (subheight - window_height) / 2;
  
  int row = 0;
  int column = 0;

  for(ListElement<window>* le=windowlist.first(); le; le=le->next())
    {
      window* win = le->data();
      int xoffset = x + column * (subwidth  + xmargin) + extra_xoffset;
      int yoffset = y + row    * (subheight + ymargin) + extra_yoffset;

      if(win->window_subject_to_layout)
	{
	  //printf("  layout is resizing window to (%d,%d) %dx%d\n", xoffset, yoffset, window_width, window_height);
	  win->resize(xoffset, yoffset, window_width, window_height);
	  win->layout();
	  
	  row++;
	  if(row==num_rows_best)
	    {
	      row=0;
	      column++;
	    }
	}
    }
}


void horizontal_layout_plan::do_layout(int x, int y, int w, int h, int xmargin, int ymargin, List<window>& windowlist, int aspect_x, int aspect_y, bool force_aspect)
{
  int num_windows = 0;  

  for(ListElement<window>* le=windowlist.first(); le; le=le->next())
    {
      window* win = le->data();
      if(win->window_subject_to_layout)
	num_windows++;
    }

  if(num_windows==0)
    return;

  printf("Horizontally laying out %d windows to (%d,%d) (%dx%d).\n", num_windows, x, y, w, h);

  int design_xoffset = x;
  int design_yoffset = y;
  int design_width  = w;
  int design_height = h;
  
  for(ListElement<window>* le=windowlist.first(); le; le=le->next())
    {
      int window_width  = (design_width-xmargin*(num_windows-1))/num_windows;
      int window_height = design_height;

      int xoffset = design_xoffset;
      int yoffset = design_yoffset;

      window* win = le->data();

      if(win->window_subject_to_layout)
	{
	  printf("  layout is resizing window to (%d,%d) %dx%d\n", xoffset, yoffset, window_width, window_height);
	  win->resize(xoffset, yoffset, window_width, window_height);
	  win->layout();
	  
	  design_xoffset += win->width + xmargin;
	  design_width   -= win->width + xmargin;
	  num_windows--;
	}
    }
}


void vertical_layout_plan::do_layout(int x, int y, int w, int h, int xmargin, int ymargin, List<window>& windowlist, int aspect_x, int aspect_y, bool force_aspect)
{
  //printf("In horizontal do_layout.  (%d,%d) %dx%d\n", x, y, w, h);

  int num_windows = 0;  

  for(ListElement<window>* le=windowlist.first(); le; le=le->next())
    {
      window* win = le->data();
      if(win->window_subject_to_layout)
	num_windows++;
    }

  if(num_windows==0)
    return;

  printf("Vertically laying out %d windows to (%d,%d) (%dx%d).\n", num_windows, x, y, w, h);

  int design_xoffset = x;
  int design_yoffset = y;
  int design_width  = w;
  int design_height = h;

  //
  // First, lay things out where they can have all the height they
  // want.  If that works, go with it.
  //
  for(ListElement<window>* le=windowlist.first(); le; le=le->next())
    {
      int window_width  = design_width;
      int window_height = design_height;

      int xoffset = design_xoffset;
      int yoffset = design_yoffset;

      window* win = le->data();

      if(win->window_subject_to_layout)
	{
	  printf("  layout is resizing window to (%d,%d) %dx%d\n", xoffset, yoffset, window_width, window_height);
	  win->resize(xoffset, yoffset, window_width, window_height);
	  win->layout();

	  printf("     window took (%dx%d)\n", win->width, win->height);

	  design_yoffset += win->height + ymargin;
	  design_height  -= win->height + ymargin;
	}
    }

  //
  // See if it worked
  //
  window* last_win = windowlist.last()->data();
  int last_y_position = last_win->offset.y + last_win->height;

  if(last_y_position > h)
    {
      printf("Height was %d, larger than the available %d.\n", last_y_position, h);
    }

  //
  // Set the width of the total to the max of the widths
  //
  int max_width = 0;
  for(ListElement<window>* le=windowlist.first(); le; le=le->next())
    {
      window* win = le->data();
      if(win->width > max_width)
	max_width = win->width;
    }

  last_win->parent->width = max_width;
}


