/* 
 * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include "stdafx.h"

#include "mforms/mforms.h"
#include "wf_view.h"
#include "wf_table.h"
#include "wf_label.h"

using namespace MySQL;
using namespace MySQL::Forms;
using namespace MySQL::Utilities;

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->IsHandleCreated)
    return false;

  if (table->RowCount > 0 && table->ColumnCount > 0)
  {
    ControlUtilities::SuspendDrawing(table);

    ViewImpl::adjust_auto_resize_from_docking(table);
    System::Drawing::Size tableSize = table->ComputeLayout(table->Size, true);

    bool parentLayoutNeeded= !table->Size.Equals(tableSize);
    if (parentLayoutNeeded)
      ViewImpl::resize_with_docking(table, tableSize);

    ControlUtilities::ResumeDrawing(table);
    table->Refresh();

    return parentLayoutNeeded;
  }
  return false;
}

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

/**
 * Computes the entire layout of the table. This includes size and position of client controls.
 * 
 * @param proposedSize The size to start from layouting. Since super ordinated controls may impose
 *                     a layout size we need to honor that (especially important for auto wrapping
 *                     labels).
 * @param resizeChildren Tells the function whether the computed client control bounds should be applied
 *                       (when doing a relayout) or not (when computing the preferred size).
 * @return The resulting size of the table.
 */
System::Drawing::Size Table::ComputeLayout(System::Drawing::Size proposedSize, bool resizeChildren)
{
  // 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 children 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>(RowCount);
  for (int i= 0; i < RowCount; i++)
    heights[i]= 0;
  array<int>^ widths= gcnew array<int>(ColumnCount);
  for (int i= 0; i < ColumnCount; i++)
    widths[i]= 0;

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

  proposedSize.Width -= Padding.Horizontal;
  proposedSize.Height -= Padding.Vertical;

  System::Drawing::Size newSize= System::Drawing::Size::Empty;

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

    // 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.
    // However, if the current entry is expanding and covers a column which is already set to expand
    // then don't apply expansion, as we only want to expand those columns.
    int currentWidth = 0;
    bool doExpand = entry->horizontalExpand;
    if (doExpand)
    {
      for (int i = entry->leftAttachment; i < entry->rightAttachment; i++)
      {
        if (horizontalExpandState[i])
        {
          doExpand = false;
          break;
        }
      }
    }
    for (int i = entry->leftAttachment; i < entry->rightAttachment; i++)
    {
      currentWidth += widths[i];
      if (doExpand)
        horizontalExpandState[i] = true;
    }

    bool use_min_width = ViewImpl::use_min_width_for_layout(entry->control);
    bool use_min_height = ViewImpl::use_min_height_for_layout(entry->control);
    if (use_min_width && use_min_height)
      entry->bounds.Size = entry->control->MinimumSize;
    else
    {
      entry->bounds= System::Drawing::Rectangle(Point(0, 0),
        entry->control->GetPreferredSize(System::Drawing::Size(currentWidth, 0)));
      if (use_min_width)
        entry->bounds.Width= entry->control->MinimumSize.Width;
      if (use_min_height)
        entry->bounds.Height= entry->control->MinimumSize.Height;
    }

    // Weird behavior of combo boxes. They report a one pixel too small height (you cannot set them
    // to this smaller height, though). So this wrong value is messing up our computed overall height.
    if (entry->control->GetType() == ComboBox::typeid)
      entry->bounds.Height++;

    // 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.

    // If the width of the entry is larger then distribute the difference to all cells it covers.
    if (entry->bounds.Width > currentWidth)
    {
      // The fraction is a per-cell value and computed by an integer div (we cannot add partial pixels).
      // Hence we might have a rest, which is less than the span size. Distribute this rest over all spanned cell too.
      int fraction= (entry->bounds.Width - currentWidth) / (entry->rightAttachment - entry->leftAttachment);
      int rest= (entry->bounds.Width - currentWidth) % (entry->rightAttachment - entry->leftAttachment);

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

  // Once we got the minimal width we need to compute the real width as the height computation depends
  // on the final column widths (e.g. for wrapping labels that change their height depending on their width).
  // Handle homogeneous mode.
  if (Homogeneous)
  {
    int max= 0;
    for (int i= 0; i < ColumnCount; i++)
      if (widths[i] > max)
        max= widths[i];

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

  // Compute overall width and handle expanded entries.
  for (int i= 0; i < ColumnCount; i++)
    newSize.Width += widths[i];
  newSize.Width += (ColumnCount - 1) * ColumnSpacing;

  // Do auto sizing the table if enabled. Apply minimal bounds in any case.
  if (newSize.Width > proposedSize.Width || ViewImpl::can_auto_resize_horizontally(this))
  {
    proposedSize.Width= newSize.Width;
    if (proposedSize.Width < MinimumSize.Width)
      proposedSize.Width= MinimumSize.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 < proposedSize.Width)
  {
    int expandCount= 0;
    for (int i= 0; i < ColumnCount; i++)
      if (horizontalExpandState[i])
        expandCount++;
    if (expandCount > 0)
    {
      int fraction= (proposedSize.Width - newSize.Width) / expandCount;
      for (int i= 0; i < ColumnCount; i++)
        if (horizontalExpandState[i])
          widths[i] += fraction;
    }
  }

  // Second round: Now that we have all final widths compute the heights. Start with sorting the entries
  // 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.
    // Same expansion handling here as for horizontal expansion.
    int currentHeight= 0;
    bool doExpand = entry->verticalExpand;
    if (doExpand)
    {
      for (int i = entry->topAttachment; i < entry->bottomAttachment; i++)
      {
        if (verticalExpandState[i])
        {
          doExpand = false;
          break;
        }
      }
    }
    for (int i= entry->topAttachment; i < entry->bottomAttachment; i++)
    {
      currentHeight += heights[i];
      if (doExpand)
        verticalExpandState[i]= true;
    }

    // For controls that change height depending on their width we need another preferred size computation.
    // Currently this applies only for wrapping labels.
    if (entry->control->GetType() == WrapControlLabel::typeid)
    {
      int currentWidth= 0;
      for (int i= entry->leftAttachment; i < entry->rightAttachment; i++)
        currentWidth += widths[i];
      entry->bounds= System::Drawing::Rectangle(Point(0, 0),
        entry->control->GetPreferredSize(System::Drawing::Size(currentWidth, 0)));
    }

    // 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);
      int rest= (entry->bounds.Height - currentHeight) % (entry->bottomAttachment - entry->topAttachment);

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

  // Handle homogeneous mode.
  if (Homogeneous)
  {
    int max= 0;
    for (int i= 0; i < 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 (proposedSize.Height > max * RowCount)
      max = proposedSize.Height / RowCount;
    for (int i= 0; i < RowCount; i++)
      heights[i]= max;
  }

  // Compute overall size and handle expanded entries.
  for (int i= 0; i < RowCount; i++)
    newSize.Height += heights[i];
  newSize.Height += (RowCount - 1) * RowSpacing;

  // Do auto sizing the table if enabled. Apply minimal bounds in any case.
  if (newSize.Height > proposedSize.Height || ViewImpl::can_auto_resize_vertically(this))
  {
    proposedSize.Height= newSize.Height;
    if (proposedSize.Height < MinimumSize.Height)
      proposedSize.Height= MinimumSize.Height;
  }

  // Handle expansion of cells (vertical case). Since this can happen only if the new size is
  // less than the proposed size it does not matter for pure size computation (in that case we
  // have already our target size). Hence do it only if we are actually layouting the table.
  if (resizeChildren)
  {
    if (newSize.Height < proposedSize.Height)
    {
      int expandCount= 0;
      for (int i= 0; i < RowCount; i++)
        if (verticalExpandState[i])
          expandCount++;
      if (expandCount > 0)
      {
        int fraction= (proposedSize.Height - newSize.Height) / expandCount;
        for (int i= 0; i < 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>(RowCount + 1);
    rowStarts[0]= Padding.Top;
    for (int i= 1; i <= RowCount; i++)
      rowStarts[i]= rowStarts[i - 1] + heights[i - 1] + RowSpacing;

    array<int>^ columnStarts= gcnew array<int>(ColumnCount + 1);
    columnStarts[0]= Padding.Left;
    for (int i= 1; i <= ColumnCount; i++)
      columnStarts[i]= columnStarts[i - 1] + widths[i - 1] + 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] - ColumnSpacing;
      entry->bounds.Height= rowStarts[entry->bottomAttachment] - rowStarts[entry->topAttachment] - RowSpacing;
    }

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

  }

  proposedSize.Width += Padding.Horizontal;
  proposedSize.Height += Padding.Vertical;

  return proposedSize;
}

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

/**
 * Returns the preferred size of the table (that is, the minimal size that can cover all content). 
 *
 * @param proposedSize The base size to be used for layout. The resulting size won't usually go
 *                     below this size.
 *
 * @return The preferred size of this table.
 */
System::Drawing::Size Table::GetPreferredSize(System::Drawing::Size proposedSize)
{
  return ComputeLayout(proposedSize, false);
}

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

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);
    self->set_layout_dirty(true);
  }
}

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

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);
    self->set_layout_dirty(true);
  }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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