/* 
 * © 2009 Sun Microsystems, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#include "stdafx.h"
#include "wf_table.h"

using namespace MySQL;
using namespace MySQL::Forms;

using namespace System::Drawing;

//--------------------------------------------------------------------------------------------------

/**
 * Applies the computed bounds to each control. Adjust position if the control does not fill the given
 * space depending on the fill flag.
 *
 * @param list The list of cell entries with their bounds to be applied.
 */
void apply_bounds(CellList% list)
{
  for each (CellEntry^ entry in list)
  {
    ViewImpl::remove_auto_resize(entry->control, mforms::ResizeBoth);
    if (entry->horizontalFill)
      entry->control->Width= entry->bounds.Width;
    if (entry->verticalFill)
      entry->control->Height= entry->bounds.Height;

    // Get the resulting control size and compute the final position.
    int actual_width= entry->control->Width;
    int actual_height= entry->control->Height;
    
    int left= entry->bounds.Left + (entry->bounds.Width - actual_width) / 2;
    int top= entry->bounds.Top + (entry->bounds.Height - actual_height) / 2;
    entry->control->Location= Point(left, top);
  }
}

//--------------------------------------------------------------------------------------------------

/**
 * Helper method to compare two cell entries by their column span size.
 */
int CompareColumnSpan(CellEntry^ entry1, CellEntry^ entry2)
{
  if (entry1 == nullptr)
  {
    if (entry2 == nullptr)
      return 0; // Both entries are null, so they are equal.
    else
      return -1; // entry2 is greater since it is not null, but entry1 is.
  }
  else
  {
    if (entry2 == nullptr)
      return 1; // entry1 is not null, but entry2 is, so entry1 is greater.
    else
    {
      // Now to the real work. Both entries are valid.
      int difference= (entry1->rightAttachment - entry1->leftAttachment) - (entry2->rightAttachment - entry2->leftAttachment);
      return (difference > 0) ? 1 : ((difference < 0) ? -1 : 0);
    }
  }
}

//--------------------------------------------------------------------------------------------------

/**
* Helper method to compare two cell entries by their row span size.
*/
int CompareRowSpan(CellEntry^ entry1, CellEntry^ entry2)
{
  if (entry1 == nullptr)
  {
    if (entry2 == nullptr)
      return 0; // Both entries are null, so they are equal.
    else
      return -1; // entry2 is greater since it is not null, but entry1 is.
  }
  else
  {
    if (entry2 == nullptr)
      return 1; // entry1 is not null, but entry2 is, so entry1 is greater.
    else
    {
      // Now to the real work. Both entries are valid.
      int difference= (entry1->bottomAttachment - entry1->topAttachment) - (entry2->bottomAttachment - entry2->topAttachment);
      return (difference > 0) ? 1 : ((difference < 0) ? -1 : 0);
    }
  }
}

//----------------- GtkTableLayout ----------------------------------------------------------------

/**
 * This is the main layout method of the GTK table layout engine. It is triggered automatically
 * by the .NET framework, whenever a need appears to re-layout the tables content (usually when child controls
 * are added or removed, or when the table is resized, e.g. as part of the layout process of its parent).
 *
 * @param container The control which must be laid out.
 * @param arguments A number of arguments which are supposed to control the layouting process. Not used currently.
 *
 * @return True if the parent should re-layout itself (e.g. due to a size change of this table) or false, if not.
 */
bool GtkTableLayout::Layout(Object^ container, LayoutEventArgs^ arguments)
{
  Table^ table= (Table^) container;

  if (table->RowCount > 0 && table->ColumnCount > 0)
  {
    CellList% content= table->Content;

    // Layouting the grid goes like this:
    // * Compute all row heights + column widths.
    // * Apply the resulting cell sizes to all attached children.
    // * Adjust the size of the container if necessary.

    // To compute all row heights and widths do:
    // 1) For each cell entry
    // 2)   Keep the expand state for all cells it covers.
    // 3)   Compute the base cell sizes for all cells it covers (e.g. for the width: take the control width and distribute
    //      it evenly over all cells covered from left to right attachment).
    // 4)   Compare the cell size with what has been computed overall so far. Replace any cell size for 
    //      the control which is larger than what is stored so far by that larger value.
    
    // If in homogeneous mode do:
    // Find the tallest row and apply its height to all other rows so they are all at the same height.
    // Similar for columns.

    // If the sum of all rows is smaller then the current container height distribute the difference evenly
    // over all childs with expand flag set.
    // Similar for columns.

    // If not in homogeneous mode do:
    // 1) If the sum of all widths is smaller than the control width then distribute the remaining
    //     space over all columns for which the expand flag is set.
    // 2) Same for all rows.

    array<int>^ heights= gcnew array<int>(table->RowCount);
    for (int i= 0; i < table->RowCount; i++)
      heights[i]= 0;
    array<int>^ widths= gcnew array<int>(table->ColumnCount);
    for (int i= 0; i < table->ColumnCount; i++)
      widths[i]= 0;

    array<bool>^ verticalExpandState= gcnew array<bool>(table->RowCount);
    for (int i= 0; i < table->RowCount; i++)
      verticalExpandState[i]= false;
    array<bool>^ horizontalExpandState= gcnew array<bool>(table->ColumnCount);
    for (int i= 0; i < table->ColumnCount; i++)
      horizontalExpandState[i]= false;

    // First round: sort list for increasing column span count, so we can process smallest entries first.
    content.Sort(gcnew Comparison<CellEntry^>(CompareColumnSpan));

    // Go for each cell entry and apply its preferred size to the proper cells,
    // after visibility state and bounds are set.
    // Keep expand states so we can apply them later.
    for each (CellEntry^ entry in content)
    {
      entry->isVisible= entry->control->Visible && (entry->rightAttachment > entry->leftAttachment)
        && (entry->bottomAttachment > entry->topAttachment);
      if (!entry->isVisible)
        continue;

      if (ViewImpl::use_min_size_for_layout(entry->control))
      {
        entry->bounds= System::Drawing::Rectangle(Point(0, 0), entry->control->MinimumSize);

        // We need more sanity checks. Windows controls behave really strange sometimes.
        if (entry->bounds.Width == 0)
          entry->bounds.Width= entry->control->PreferredSize.Width;
        if (entry->bounds.Height == 0)
          entry->bounds.Height= entry->control->PreferredSize.Height;
      }
      else
        entry->bounds= System::Drawing::Rectangle(Point(0, 0), entry->control->PreferredSize);

      // Set all cells to the computed partial size if it is larger than what was found so far.
      // On the way apply the expand flag to all cells that are covered by that entry.

      // Check if the width of the entry is larger than what we have already.
      // While we are at it, keep the expand state in the associated cells.
      int currentWidth= 0;
      for (int i= entry->leftAttachment; i < entry->rightAttachment; i++)
      {
        currentWidth += widths[i];
        if (entry->horizontalExpand)
          horizontalExpandState[i]= true;
      }

      // If the width of the entry is larger then distribute the difference to all cells it covers.
      if (entry->bounds.Width > currentWidth)
      {
        int fraction= (entry->bounds.Width - currentWidth) / (entry->rightAttachment - entry->leftAttachment);

        for (int i= entry->leftAttachment; i < entry->rightAttachment; i++)
          widths[i] += fraction;
      }
    }

    // Second round: sort list for increasing row span count, so we can process smallest entries first.
    content.Sort(gcnew Comparison<CellEntry^>(CompareRowSpan));

    // Go for each cell entry and apply its preferred size to the proper cells.
    // Keep expand states so we can apply them later.
    for each (CellEntry^ entry in content)
    {
      // Visibility state and bounds where already determined in the first round.
      if (!entry->isVisible)
        continue;

      // Set all cells to the computed partial size if it is larger than what was found so far.
      // On the way apply the expand flag to all cells that are covered by that entry.

      // Check if the height of the entry is larger than what we have already.
      int currentHeight= 0;
      for (int i= entry->topAttachment; i < entry->bottomAttachment; i++)
      {
        currentHeight += heights[i];
        if (entry->verticalExpand)
          verticalExpandState[i]= true;
      }

      // If the height of the entry is larger then distribute the difference to all cells it covers.
      if (entry->bounds.Height > currentHeight)
      {
        int fraction= (entry->bounds.Height - currentHeight) / (entry->bottomAttachment - entry->topAttachment);

        for (int i= entry->topAttachment; i < entry->bottomAttachment; i++)
          heights[i] += fraction;
      }
    }

    int padding= table->Padding.All;
    Size tableSize= table->Size;
    tableSize.Width -= 2 * padding;
    tableSize.Height -= 2 * padding;

    // Handle homogeneous mode.
    if (table->Homogeneous)
    {
      int max= 0;
      for (int i= 0; i < table->RowCount; i++)
        if (heights[i] > max)
          max= heights[i];

      // If the sum of all resized rows still does not fill the full height
      // then distribute the remaining space too to make them fill.
      if (tableSize.Height > max * table->RowCount)
        max = tableSize.Height / table->RowCount;
      for (int i= 0; i < table->RowCount; i++)
        heights[i]= max;
      max= 0;
      for (int i= 0; i < table->ColumnCount; i++)
        if (widths[i] > max)
          max= widths[i];

      if (tableSize.Width > max * table->ColumnCount)
        max = tableSize.Width / table->ColumnCount;
      for (int i= 0; i < table->ColumnCount; i++)
        widths[i]= max;
    }

    // Compute overall size and handle expanded entries.
    Size newSize= Size::Empty;
    for (int i= 0; i < table->RowCount; i++)
      newSize.Height += heights[i];
    newSize.Height += (table->RowCount - 1) * table->RowSpacing;
    for (int i= 0; i < table->ColumnCount; i++)
      newSize.Width += widths[i];
    newSize.Width += (table->ColumnCount - 1) * table->ColumnSpacing;

    // Do auto sizing the table if enabled. Apply minimal bounds in any case.
    if (newSize.Height > tableSize.Height || ViewImpl::can_auto_resize_vertically(table))
      tableSize.Height= newSize.Height;
    if (newSize.Width > tableSize.Width || ViewImpl::can_auto_resize_horizontally(table))
      tableSize.Width= newSize.Width;

    // Handle expansion of cells, which only applies to the table if it is not set to homogeneous mode.
    // The following test will also fail if homogeneous mode is set because the cell sizes 
    // have been adjusted already to fill the entire table.
    if (newSize.Width < tableSize.Width)
    {
      int expandCount= 0;
      for (int i= 0; i < table->ColumnCount; i++)
        if (horizontalExpandState[i])
          expandCount++;
      if (expandCount > 0)
      {
        int fraction= (tableSize.Width - newSize.Width) / expandCount;
        for (int i= 0; i < table->ColumnCount; i++)
          if (horizontalExpandState[i])
            widths[i] += fraction;
      }
    }

    if (newSize.Height < tableSize.Height)
    {
      int expandCount= 0;
      for (int i= 0; i < table->RowCount; i++)
        if (verticalExpandState[i])
          expandCount++;
      if (expandCount > 0)
      {
        int fraction= (tableSize.Height - newSize.Height) / expandCount;
        for (int i= 0; i < table->RowCount; i++)
          if (verticalExpandState[i])
            heights[i] += fraction;
      }
    }

    // Compute target bounds from cell sizes. Compute one more column/row used as right/bottom border.
    array<int>^ rowStarts= gcnew array<int>(table->RowCount + 1);
    rowStarts[0]= padding;
    for (int i= 1; i <= table->RowCount; i++)
      rowStarts[i]= rowStarts[i - 1] + heights[i - 1] + table->RowSpacing;

    array<int>^ columnStarts= gcnew array<int>(table->ColumnCount + 1);
    columnStarts[0]= padding;
    for (int i= 1; i <= table->ColumnCount; i++)
      columnStarts[i]= columnStarts[i - 1] + widths[i - 1] + table->ColumnSpacing;

    for each (CellEntry^ entry in content)
    {
      if (!entry->isVisible)
        continue;

      entry->bounds.X= columnStarts[entry->leftAttachment];
      entry->bounds.Y= rowStarts[entry->topAttachment];
      entry->bounds.Width= columnStarts[entry->rightAttachment] - columnStarts[entry->leftAttachment] - table->ColumnSpacing;
      entry->bounds.Height= rowStarts[entry->bottomAttachment] - rowStarts[entry->topAttachment] - table->RowSpacing;
    }

    // Apply target bounds to cell content.
    apply_bounds(content);

    // Finally resize container if necessary.
    tableSize.Width += 2 * padding;
    tableSize.Height += 2 * padding;

    bool parentLayoutNeeded= !table->Size.Equals(tableSize);
    if (parentLayoutNeeded)
      table->Size= tableSize;

    return parentLayoutNeeded;
  }
  return false;
}

//----------------- Table ---------------------------------------------------------------------------

/**
 * Returns the preferred size of the table (that is, the minimal size that can cover all content). 
 * This size is cached once it was computed to save relayouting the table over and over again
 * unless the layout of the table has changed.
 *
 * @param proposedSize Not used.
 *
 * @return The preferred size of this table.
 */
Drawing::Size Table::GetPreferredSize(Drawing::Size proposedSize)
{
  if (ViewImpl::is_layout_dirty(this))
  {
    ViewImpl::set_layout_dirty(this, false);
    ViewImpl::set_full_auto_resize(this);
    PerformLayout();
    preferredSize = Size;
  }
  return preferredSize;
}

//-------------------------------------------------------------------------------------------------

void Table::Add(Control^ control, int left, int right, int top, int bottom, int flags)
{
  ViewImpl::set_layout_dirty(this, true);

  CellEntry^ entry= gcnew CellEntry();
  entry->control= control;

  // Do some sanity checks here to avoid later trouble.
  // Upper limits are not checked as this can change with the number of columns,
  // even after the table was filled.
  if (left < 0)
    left= 0;
  if (left > right)
  {
    int temp= left;
    left= right;
    right= temp;
  }
  if (left == right)
    right++;
  if (top < 0)
    top= 0;
  if (top > bottom)
  {
    int temp= top;
    top= bottom;
    bottom= temp;
  }
  if (top == bottom)
    bottom++;
  entry->leftAttachment= left;
  entry->rightAttachment= right;
  entry->topAttachment= top;
  entry->bottomAttachment= bottom;
  entry->horizontalExpand= (flags & ::mforms::HExpandFlag) != 0;
  entry->verticalExpand= (flags & ::mforms::VExpandFlag) != 0;
  entry->horizontalFill= (flags & ::mforms::HFillFlag) != 0;
  entry->verticalFill= (flags & ::mforms::VFillFlag) != 0;
  content.Add(entry); // for us
  Controls->Add(control); // for Windows
}

//-------------------------------------------------------------------------------------------------

void Table::Remove(Control^ control)
{
  ViewImpl::set_layout_dirty(this, true);
  Controls->Remove(control);

  for each (CellEntry^ entry in content)
  {
    if (entry->control == control)
    {
      content.Remove(entry);
      break;
    }
  }
}

//----------------- TableImpl -----------------------------------------------------------------------

bool TableImpl::create(::mforms::Table *self)
{
  TableImpl ^table= gcnew TableImpl(self);

  if (table != nullptr)
  {
    Table^ native_table= ViewImpl::create<Table>(self, table);
    return true;
  }
  return false;
}

//-------------------------------------------------------------------------------------------------

void TableImpl::add(mforms::Table *self, mforms::View *child, int left, int right, int top, int bottom, int flags)
{
  TableImpl^ table= (TableImpl^)ObjectImpl::FromUnmanaged(self);
  if (table != nullptr)
  {
    ViewImpl^ child_view= (ViewImpl^)ObjectImpl::FromUnmanaged(child);
    table->add(child_view->get_control<Control>(), left, right, top, bottom, flags);
  }
}

//-------------------------------------------------------------------------------------------------

void TableImpl::remove(mforms::Table *self, mforms::View* child)
{
  TableImpl^ table= (TableImpl^)ObjectImpl::FromUnmanaged(self);
  if (table != nullptr)
  {
    ViewImpl^ child_view= (ViewImpl^)ObjectImpl::FromUnmanaged(child);
    table->remove(child_view->get_control<Control>());
  }
}

//-------------------------------------------------------------------------------------------------

void TableImpl::set_row_count(mforms::Table *self, int count)
{
  TableImpl^ table= (TableImpl^)ObjectImpl::FromUnmanaged(self);
  if (table != nullptr)
    table->set_row_count(count);
}

//-------------------------------------------------------------------------------------------------

void TableImpl::set_column_count(mforms::Table *self, int count)
{
  TableImpl^ table= (TableImpl^)ObjectImpl::FromUnmanaged(self);
  if (table != nullptr)
    table->set_column_count(count);
}

//-------------------------------------------------------------------------------------------------

void TableImpl::set_row_spacing(mforms::Table *self, int space)
{
  TableImpl^ table= (TableImpl^)ObjectImpl::FromUnmanaged(self);
  if (table != nullptr)
    table->set_row_spacing(space);
}

//-------------------------------------------------------------------------------------------------

void TableImpl::set_column_spacing(mforms::Table *self, int space)
{
  TableImpl^ table= (TableImpl^)ObjectImpl::FromUnmanaged(self);
  if (table != nullptr)
    table->set_column_spacing(space);
}

//-------------------------------------------------------------------------------------------------

void TableImpl::set_padding(mforms::Table *self, int padding)
{
  TableImpl^ table= (TableImpl^)ObjectImpl::FromUnmanaged(self);
  if (table != nullptr)
    table->set_padding(padding);
}

//-------------------------------------------------------------------------------------------------

void TableImpl::set_homogeneous(mforms::Table *self, bool value)
{
  TableImpl^ table= (TableImpl^)ObjectImpl::FromUnmanaged(self);
  if (table != nullptr)
    table->set_homogeneous(value);
}

//-------------------------------------------------------------------------------------------------

void TableImpl::add(Windows::Forms::Control ^control, int left, int right, int top, int bottom, int flags)
{
  Table ^table= get_control<Table>();
  table->Add(control, left, right, top, bottom, flags);
  LayoutDirty= true;
}

//-------------------------------------------------------------------------------------------------

void TableImpl::remove(Windows::Forms::Control ^control)
{
  Table ^table= get_control<Table>();
  table->Remove(control);
  LayoutDirty= true;
}

//-------------------------------------------------------------------------------------------------

void TableImpl::set_homogeneous(bool homogeneous)
{
  get_control<Table>()->Homogeneous= homogeneous;
  LayoutDirty= true;
}

//-------------------------------------------------------------------------------------------------

void TableImpl::set_row_spacing(int space)
{
  get_control<Table>()->RowSpacing= space;
  LayoutDirty= true;
}

//-------------------------------------------------------------------------------------------------

void TableImpl::set_column_spacing(int space)
{
  get_control<Table>()->ColumnSpacing= space;
  LayoutDirty= true;
}

//-------------------------------------------------------------------------------------------------

void TableImpl::set_row_count(int count)
{
  get_control<Table>()->RowCount= count;
  LayoutDirty= true;
}

//-------------------------------------------------------------------------------------------------

void TableImpl::set_column_count(int count)
{
  get_control<Table>()->ColumnCount= count;
  LayoutDirty= true;
}

//-------------------------------------------------------------------------------------------------

void TableImpl::set_padding(int padding)
{
  get_control<Table>()->Padding= Padding(padding);
  LayoutDirty= true;
}

//-------------------------------------------------------------------------------------------------

