/* 
 * © 2008-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_box.h"

using namespace MySQL;
using namespace MySQL::Forms;

using namespace System::Drawing;

ref class ControlEntry {
public:
  Control^ control;
  System::Drawing::Rectangle bounds;
};

typedef List<ControlEntry^> ControlList;

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

/**
 * Adjusts all child bounds to fill their container vertically (in horizontal mode)
 * or horizontally (in vertical mode). Applies the given padding value accordingly.
 *
 * Note: containerSize must not contain any padding value.
 * 
 */
void maximize_children(ControlList% list, bool horizontal, Size containerSize, int padding)
{
  if (horizontal)
  {
    for each (ControlEntry^ entry in list)
    {
      ViewImpl::remove_auto_resize(entry->control, mforms::ResizeVertical);
      entry->bounds.Y= padding;
      entry->bounds.Height= containerSize.Height;
    };
  }
  else
  {
    for each (ControlEntry^ entry in list)
    {
      ViewImpl::remove_auto_resize(entry->control, mforms::ResizeHorizontal);
      entry->bounds.X= padding;

      // Determine the preferred size based on the new width (for labels, as they can be in auto wrap mode).
      if (entry->control->GetType()->IsSubclassOf(Label::typeid))
        entry->bounds.Size= entry->control->GetPreferredSize(Size(containerSize.Width, 0));
      else
        entry->bounds.Width= containerSize.Width;
    };
  }
}

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

/**
 * Applies the computed bounds to each control. Adjust position if the control does not fill the given
 * space depending on the fill flag.
 */
void apply_bounds(ControlList% list, Box^ box, bool horizontal)
{
  for each (ControlEntry^ entry in list)
  {
    // The parameter horizontal tells us if the layout direction is horizontal or vertical.
    // Always resize in layout direction but do resize in the other direction only if fill is set.
    ViewImpl::remove_auto_resize(entry->control, mforms::ResizeBoth);
    if (horizontal)
    {
      entry->control->Height= entry->bounds.Height;
      if (box->GetControlFills(entry->control))
        entry->control->Width= entry->bounds.Width;
    }
    else
    {
      entry->control->Width= entry->bounds.Width;
      if (box->GetControlFills(entry->control))
        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);
  }
}

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

/**
 * Returns the largest width of any control in both given lists.
 */
int get_largest_width(ControlList% list1, ControlList% list2)
{
  int max= 0;
  for each (ControlEntry^ entry in list1)
  {
    if (entry->bounds.Width > max)
      max= entry->bounds.Width;
  }
  for each (ControlEntry^ entry in list2)
  {
    if (entry->bounds.Width > max)
      max= entry->bounds.Width;
  }

  return max;
}

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

/**
 * Returns the largest height of any control in both given lists.
 */
int get_largest_height(ControlList% list1, ControlList% list2)
{
  int max= 0;
  for each (ControlEntry^ entry in list1)
  {
    if (entry->bounds.Height > max)
      max= entry->bounds.Height;
  }
  for each (ControlEntry^ entry in list2)
  {
    if (entry->bounds.Height > max)
      max= entry->bounds.Height;
  }

  return max;
}

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

/**
 * Called if the container is in "use all" mode and any remaining space in it is to be distributed
 * amongst all child controls that have the expand flag set.
 *
 * @param list The list of controls to expand.
 * @param fraction The amount of pixels the controls must be resized/moved.
 * @param mirrored Do the adjustment in a mirrored fashion (for right aligned controls).
 */
void expand_horizontally(ControlList% list, Box^ box, int fraction, bool mirrored)
{
  for (int i= 0; i < list.Count; i++)
  {
    if (box->GetControlExpands(list[i]->control))
    {
      list[i]->bounds.Width += fraction;
      if (mirrored)
      {
        list[i]->bounds.X -= fraction;

        // Move all following controls by the same amount.
        for (int j= i + 1; j < list.Count; j++)
          list[j]->bounds.X -= fraction;
      }
      else
      {
        // Move all following controls by the same amount.
        for (int j= i + 1; j < list.Count; j++)
          list[j]->bounds.X += fraction;
      }
    }
  }
}

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

/**
 * Called if the container is in "use all" mode and any remaining space in it is to be distributed
 * amongst all child controls that have the expand flag set.
 *
 * @param list The list of controls to expand.
 * @param fraction The amount of pixels the controls must be resized/moved.
 * @param mirrored Do the adjustment in a mirrored fashion (for bottom aligned controls).
 */
void expand_vertically(ControlList% list, Box^ box, int fraction, bool mirrored)
{
  for (int i= 0; i < list.Count; i++)
  {
    if (box->GetControlExpands(list[i]->control))
    {
      list[i]->bounds.Height += fraction;
      if (mirrored)
      {
        list[i]->bounds.Y -= fraction;

        // Move all following controls by the same amount.
        for (int j= i + 1; j < list.Count; j++)
          list[j]->bounds.Y -= fraction;
      }
      else
      {
        // Move all following controls by the same amount.
        for (int j= i + 1; j < list.Count; j++)
          list[j]->bounds.Y += fraction;
      }
    }
  }
}

//----------------- HorizontalGtkBoxLayout ---------------------------------------------------------

bool HorizontalGtkBoxLayout::Layout(Object^ container, LayoutEventArgs^ arguments)
{
  Box^ box= (Box^) container;

  ControlList left_aligned;
  ControlList right_aligned;

  // Get work area of the parent box without the padding area.
  int padding= box->Padding.All;
  Size boxSize= box->Size;
  boxSize.Width -= 2 * padding;
  boxSize.Height -= 2 * padding;

  int spacing= box->Spacing;
  int max_height= 0;
  int expand_count= 0;

  // First part: setup and vertical position and size computation.
  for each (Control^ control in box->Controls)
  {
    if (!control->Visible)
      continue;

    // Make a copy of the current bounds of the control.
    // This is used in the computation before we finally manipulate the control itself.
    ControlEntry^ entry= gcnew ControlEntry();
    entry->control= control;

    if (ViewImpl::use_min_size_for_layout(control))
      entry->bounds= System::Drawing::Rectangle(Point(0, 0), control->MinimumSize);
    else
      entry->bounds= System::Drawing::Rectangle(Point(0, 0), control->GetPreferredSize(Size(0, boxSize.Height)));

    // Sort control into the proper alignment list.
    if (control->Dock == DockStyle::Right)
      right_aligned.Add(entry);
    else
      left_aligned.Add(entry);

    // Keep track of the heighest control, so we can adjust the container's height properly.
    if (max_height < entry->bounds.Height)
      max_height= entry->bounds.Height;

    // Count how many children have the expand flag set. This is needed for later computation.
    // Remove auto resizing in layout direction too, as this is mutual exclusive to epxand mode.
    if (box->GetControlExpands(control))
      expand_count++;
  }

  // Adjust height of the container if it is too small or auto resizing is enabled.
  if (boxSize.Height < max_height || ViewImpl::can_auto_resize_vertically(box))
    boxSize.Height= max_height;

  // Go again through the child list and adjust the height of each child control as well as
  // compute their vertical position.
  maximize_children(left_aligned, true, boxSize, padding);
  maximize_children(right_aligned, true, boxSize, padding);

  // Second part: horizontal position and size computation.
  // We can have two special cases here: distributed and "use all" mode, but only if the container
  // is not set to auto resizing in horizontal direction (otherwise it adjusts itself to the content).
  int common_width= 0;

  int control_count= left_aligned.Count + right_aligned.Count;
  if (control_count > 0 && box->Homogeneous)
  {
    // In this mode we resize all controls so that they entirely fill the width of the container.
    // However, if any of the child controls has a width larger than the computed common width
    // instead use this largest width and increase the container width accordingly.
    common_width= (boxSize.Width - (control_count - 1) * spacing) / control_count;
    int max= get_largest_width(left_aligned,  right_aligned);
    if (max > common_width)
      common_width= max;
  }

  int offset= padding;

  int resulting_width= 0;
  for each (ControlEntry^ entry in left_aligned)
  {
    // Consider either a common width or the individual widths of the controls here.
    entry->bounds.X= offset;
    if (common_width > 0)
      entry->bounds.Width= common_width;
    offset += entry->bounds.Width + spacing;
  }

  if (offset > padding)
  {
    // Remove the left padding we used for positioning. It's applied later.
    resulting_width = offset - padding;

    // Remove also the last spacing if there are no right aligned children.
    if (right_aligned.Count == 0)
      resulting_width -= spacing;
  }

  // For right aligned controls we first compute relative coordinates.
  offset= -padding;
  for each (ControlEntry^ entry in right_aligned)
  {
    // Consider either a common width or the individual widths of the controls here.
    if (common_width > 0)
      entry->bounds.Width= common_width;
    entry->bounds.X= offset - entry->bounds.Width;
    offset -= entry->bounds.Width + spacing;
  }

  if (offset < -padding)
    // Remove one spacing we added too much above. Also remove the left padding we used for positioning.
    // The padding is applied later.
    resulting_width += -offset - spacing - padding;

  // Adjust width of the container if it is too small or auto resizing is enabled.
  if (boxSize.Width < resulting_width || ViewImpl::can_auto_resize_horizontally(box))
    boxSize.Width= resulting_width;

  // Distribute any free space amongst all child controls which have their expand flag set. This is 
  // mutually exclusive with auto resizing in layout direction.
  // Though we don't need to test the auto resizing flag since the box's width would here already be set to
  // the resulting width we computed above if it were enabled.
  if (expand_count > 0 && boxSize.Width > resulting_width)
  {
    int fraction= (boxSize.Width - resulting_width) / expand_count;
    expand_horizontally(left_aligned, box, fraction, false);
    expand_horizontally(right_aligned, box, fraction, true);
  }

  // Apply the padding value again and compute the final position of the right aligned controls.
  boxSize.Width += 2 * padding;
  boxSize.Height += 2 * padding;
  for each (ControlEntry^ entry in right_aligned)
    entry->bounds.Offset(boxSize.Width, 0);

  // Finally set the size of the box and apply all child control bounds.
  bool parentLayoutNeeded= !box->Size.Equals(boxSize);
  if (parentLayoutNeeded)
    box->Size = boxSize;

  apply_bounds(left_aligned, box, true);
  apply_bounds(right_aligned, box, true);

  return parentLayoutNeeded;
}

//----------------- VerticalGtkBoxLayout ----------------------------------------------------------

bool VerticalGtkBoxLayout::Layout(Object^ container, LayoutEventArgs^ arguments)
{
  Box^ box= (Box^) container;

  ControlList top_aligned;
  ControlList bottom_aligned;

  // Get work area of the parent box without the padding area.
  int padding= box->Padding.All;
  Size boxSize= box->Size;
  boxSize.Width -= 2 * padding;
  boxSize.Height -= 2 * padding;

  int spacing= box->Spacing;
  int max_width= 0;
  int expand_count= 0;

  // First part: setup and vertical position and size computation.
  for each (Control^ control in box->Controls)
  {
    if (!control->Visible)
      continue;

    // Make a copy of the current bounds of the control.
    // This is used in the computation before we finally manipulate the control itself.
    ControlEntry^ entry= gcnew ControlEntry();

    if (ViewImpl::use_min_size_for_layout(control))
      entry->bounds= System::Drawing::Rectangle(Point(0, 0), control->MinimumSize);
    else
      entry->bounds= System::Drawing::Rectangle(Point(0, 0), control->GetPreferredSize(Size(boxSize.Width, 0)));
    entry->control= control;

    // Sort control into the proper alignment list.
    if (control->Dock == DockStyle::Bottom)
      bottom_aligned.Add(entry);
    else
      top_aligned.Add(entry);

    // Keep track of the heighest control, so we can adjust the container's height properly.
    if (max_width < entry->bounds.Width)
      max_width= entry->bounds.Width;

    // Count how many children have the expand flag set. This is needed for later computation.
    // Remove auto resizing in layout direction too, as this is mutual exclusive to epxand mode.
    if (box->GetControlExpands(control))
      expand_count++;
  }

  // Adjust height of the container if it is too small or auto resizing is enabled.
  if (boxSize.Width < max_width || ViewImpl::can_auto_resize_horizontally(box))
    boxSize.Width= max_width;

  // Go again through the child list and adjust the width of each child control as well as
  // compute their vertical position.
  maximize_children(top_aligned, false, boxSize, padding);
  maximize_children(bottom_aligned, false, boxSize, padding);

  // Second part: horizontal position and size computation.
  // We can have two special cases here: distributed and "use all" mode, but only if the container
  // is not set to auto resizing in horizontal direction (otherwise it adjusts itself to the content).
  int common_height= 0;
  int control_count= top_aligned.Count + bottom_aligned.Count;
  if (control_count > 0 && box->Homogeneous)
  {
    // In this mode we resize all controls so that they entirely fill the width of the container.
    // However, if any of the child controls has a width larger than the computed common width
    // instead use this largest width and increase the container width accordingly.
    common_height= (boxSize.Height  - (control_count - 1) * spacing) / (control_count);
    int max= get_largest_height(top_aligned, bottom_aligned);
    if (max > common_height)
      common_height= max;
  }

  int offset= padding;

  int resulting_height= 0;
  for each (ControlEntry^ entry in top_aligned)
  {
    // Consider either a common height or the individual widths of the controls here.
    entry->bounds.Y= offset;
    if (common_height > 0)
      entry->bounds.Height= common_height;
    offset += entry->bounds.Height + spacing;
  }

  if (offset > padding)
  {
    // Remove the bottom padding we used for positioning. It's applied later.
    resulting_height = offset - padding;

    // Remove also the last spacing if there are no bottom aligned children.
    if (bottom_aligned.Count == 0)
      resulting_height -= spacing;
  }

  // For bottom aligned controls we first compute relative coordinates.
  offset= -padding;
  for each (ControlEntry^ entry in bottom_aligned)
  {
    // Consider either a common height or the individual widths of the controls here.
    if (common_height > 0)
      entry->bounds.Height= common_height;
    entry->bounds.Y= offset - entry->bounds.Height;
    offset -= entry->bounds.Height + spacing;
  }

  if (offset < -padding)
    // Remove one spacing we added too much above. Also remove the left padding we used for positioning.
    // The padding is applied later.
    resulting_height += -offset - spacing - padding;

  // Adjust height of the container if it is too small or auto resizing is enabled.
  if (boxSize.Height < resulting_height || ViewImpl::can_auto_resize_vertically(box))
    boxSize.Height= resulting_height;

  // Distribute any free space amongst all child controls which have their expand flag set. This is 
  // mutually exclusive with auto resizing in layout direction.
  // Though we don't need to test the auto resizing flag since the box's width would here already be set to
  // the resulting width we computed above if it were enabled.
  if (expand_count > 0 && boxSize.Height > resulting_height)
  {
    int fraction= (boxSize.Height - resulting_height) / expand_count;
    expand_vertically(top_aligned, box, fraction, false);
    expand_vertically(bottom_aligned, box, fraction, true);
  }

  // Apply the padding value again and compute the final position of the bottom aligned controls.
  boxSize.Width += 2 * padding;
  boxSize.Height += 2 * padding;
  for each (ControlEntry^ entry in bottom_aligned)
    entry->bounds.Offset(0, boxSize.Height);

  // Finally set the size of the box and apply all child control bounds.
  bool parentLayoutNeeded= !box->Size.Equals(boxSize);
  if (parentLayoutNeeded)
    box->Size = boxSize;

  apply_bounds(top_aligned, box, false);
  apply_bounds(bottom_aligned, box, false);

  return parentLayoutNeeded;
}

//----------------- Box ---------------------------------------------------------------------------

Drawing::Size Box::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;
}

//----------------- BoxImpl -----------------------------------------------------------------------

void BoxImpl::add(Windows::Forms::Control ^control, bool expand, bool fill)
{
  LayoutDirty= true;
  Box ^box= get_control<Box>();

  if (box->Horizontal)
    control->Dock= DockStyle::Left;
  else 
    control->Dock= DockStyle::Top;

  box->Add(control, expand, fill);
}

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

void BoxImpl::add_end(Windows::Forms::Control ^control, bool expand, bool fill)
{
  LayoutDirty= true;
  Box ^box= get_control<Box>();

  if (box->Horizontal)
    control->Dock= DockStyle::Right;
  else 
    control->Dock= DockStyle::Bottom;

  box->Add(control, expand, fill);
}

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

void BoxImpl::remove(Windows::Forms::Control ^control)
{
  LayoutDirty= true;
  Box ^box= get_control<Box>();

  box->Remove(control);
}

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

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

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

void BoxImpl::set_spacing(int space)
{
  LayoutDirty= true;
  get_control<Box>()->Spacing= space;
}

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

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

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

void BoxImpl::set_back_color(String ^color)
{
  get_control<Box>()->BackColor= System::Drawing::ColorTranslator::FromHtml(color);
}

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

