/* 
 * Copyright (c) 2008, 2013, 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_panel.h"

#include "ConvUtils.h"

using namespace System;

using namespace MySQL;
using namespace MySQL::Forms;
using namespace MySQL::Controls;

//----------------- FillLayout ---------------------------------------------------------------------

System::Drawing::Size FillLayout::ComputeLayout(Control^ control, System::Drawing::Size proposedSize, bool resizeChildren)
{
  if (control->Controls->Count > 0)
  {
    // Exclude any space needed to draw decoration (e.g. border) from layout processing.
    System::Drawing::Rectangle inner = control->DisplayRectangle;
    System::Drawing::Size current_size = control->Size;
    int horizontal_padding = current_size.Width - inner.Width;
    int vertical_padding = current_size.Height - inner.Height;

    Control^ content= control->Controls[0];
    proposedSize.Width -= horizontal_padding;
    proposedSize.Height -= vertical_padding;

    ViewImpl::set_full_auto_resize(content);
    System::Drawing::Size contentSize = content->GetPreferredSize(proposedSize);

    if (ViewImpl::use_min_width_for_layout(content))
      contentSize.Width= content->MinimumSize.Width;
    if (ViewImpl::use_min_height_for_layout(content))
      contentSize.Height= content->MinimumSize.Height;

    // Adjust width of the container if it is too small or auto resizing is enabled.
    if (proposedSize.Width < contentSize.Width || ViewImpl::can_auto_resize_horizontally(control))
    {
      proposedSize.Width = contentSize.Width;
      if (proposedSize.Width < control->MinimumSize.Width - control->Padding.Horizontal)
        proposedSize.Width = control->MinimumSize.Width - control->Padding.Horizontal;
    }

    // Adjust height of the container if it is too small or auto resizing is enabled.
    if (proposedSize.Height < contentSize.Height || ViewImpl::can_auto_resize_vertically(control))
    {
      proposedSize.Height = contentSize.Height;
      if (proposedSize.Height < control->MinimumSize.Height - control->Padding.Vertical)
        proposedSize.Height = control->MinimumSize.Height - control->Padding.Vertical;
    }

    if (resizeChildren)
    {
      // Now stretch the client control to fill the entire display area.
      ViewImpl::remove_auto_resize(content, AutoResizeMode::ResizeBoth);

      ValueSetter^ valueSetter= dynamic_cast<ValueSetter^>(control);
      System::Drawing::Rectangle newBounds = System::Drawing::Rectangle(inner.Location, proposedSize);

      // Windows has a nesting depth limitation, which is for 64 bit Windows versions quite low
      // (like 16 or so). Exceeding this depth causes internal trouble with the result that
      // not all the messages a window usually gets are actually sent. This in turn will break our
      // layouting so we have to ensure re-layouting does not exceed this max depth by breaking up
      // the child control bounds setting. This is only needed at every 16th level but since it is hard
      // to control that reliably we do it in panels, which do not appear too often but are almost always
      // part of such control hierarchies that have a deep nesting level.
      // If you are in need to break up a deeply nested hierarchy simply insert a panel where appropriate.
      if (!control->IsHandleCreated)
        content->Bounds= newBounds;
      else
      {
        // The value for the content can be asynchronously only if a handle is created on the parent panel.
        ApplyBoundsDelegate^ applyBoundsDelegate= gcnew ApplyBoundsDelegate(valueSetter, &ValueSetter::ApplyContentBounds);
        control->BeginInvoke(applyBoundsDelegate, gcnew array<Object^>{ newBounds });
      }
    }

    proposedSize.Width += horizontal_padding;
    proposedSize.Height += vertical_padding;
  }

  return proposedSize;
}

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

bool FillLayout::Layout(Object^ container, LayoutEventArgs^ arguments)
{
  Control^ control = (Control^) container;
  if (!ViewImpl::can_layout(control, arguments->AffectedProperty))
    return false;

  ViewImpl::adjust_auto_resize_from_docking(control);
  System::Drawing::Size newSize = ComputeLayout(control, control->Size, true);

  if (newSize.Width < control->MinimumSize.Width)
    newSize.Width= control->MinimumSize.Width;
  if (newSize.Height < control->MinimumSize.Height)
    newSize.Height= control->MinimumSize.Height;

  // Finally adjust the container.
  bool parentLayoutNeeded= !control->Size.Equals(newSize);
  if (parentLayoutNeeded)
    ViewImpl::resize_with_docking(control, newSize);

  return parentLayoutNeeded;
}

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

System::Drawing::Size FillLayout::GetPreferredSize(Control^ control, System::Drawing::Size proposedSize)
{
  return ComputeLayout(control, proposedSize, false);
}

//----------------- FillGroupBox ------------------------------------------------------------------

FillGroupBox::FillGroupBox()
{
}

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

System::Drawing::Size FillGroupBox::GetPreferredSize(System::Drawing::Size proposedSize)
{
  return layoutEngine->GetPreferredSize(this, proposedSize);
}

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

void FillGroupBox::OnPaintBackground(PaintEventArgs^ args)
{
  GroupBox::OnPaintBackground(args);

  ViewImpl ^view = ObjectImpl::get_wrapper<ViewImpl>(this);
  view->draw_background(args);
}

//----------------- FillPanel ---------------------------------------------------------------------

FillPanel::FillPanel()
{
}

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

System::Drawing::Size FillPanel::GetPreferredSize(System::Drawing::Size proposedSize)
{
  return layoutEngine->GetPreferredSize(this, proposedSize);
}

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

void FillPanel::OnPaintBackground(PaintEventArgs^ args)
{
  Panel::OnPaintBackground(args);

  ViewImpl ^view = ObjectImpl::get_wrapper<ViewImpl>(this);
  view->draw_background(args);
}

//----------------- HeaderFillPanel ----------------------------------------------------------------

HeaderFillPanel::HeaderFillPanel()
{
}

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

System::Drawing::Size HeaderFillPanel::GetPreferredSize(System::Drawing::Size proposedSize)
{
  return layoutEngine->GetPreferredSize(this, proposedSize);
}

//----------------- PanelImpl ---------------------------------------------------------------------

bool PanelImpl::create(mforms::Panel *self, mforms::PanelType panelType)
{
  PanelImpl^ panel = gcnew PanelImpl(self);
  if (panel != nullptr)
  {
    panel->type = panelType;
    Control^ control;
    switch (panel->type)
    {
    case mforms::TransparentPanel: // just a container with no background
    case mforms::FilledPanel: // just a container with color filled background
        // The background color can be specified separately and does not determine the type of panel we create.
        control= ViewImpl::create<FillPanel>(self, panel);
      break;

    case mforms::TitledBoxPanel: // native grouping box with a title with border
    case mforms::BorderedPanel: // container with native border
    case mforms::TitledGroupPanel: // native grouping container with a title (may have no border) 
      // The title can be set separately and does not determine the type of the group box we create.
      // A control with title but no border is not supported on Windows, so we need to create a special composite.
      // TODO: implement box with title, but no border.
      control = ViewImpl::create<FillGroupBox>(self, panel);
      control->Padding = Padding(7, 0, 7, 8);
      break;

    case mforms::LineBorderPanel: // container with a solid line border
      {
        FillPanel ^native_panel = ViewImpl::create<FillPanel>(self, panel);
        control = native_panel;
        native_panel->BorderStyle = BorderStyle::FixedSingle;
        break;
      }

    case mforms::FilledHeaderPanel:
    case mforms::StyledHeaderPanel: // Panel with header
      {
        HeaderFillPanel ^native_panel = ViewImpl::create<HeaderFillPanel>(self, panel);
        control = native_panel;
        control->Padding = Padding(0, 21, 0, 0);
        control->BackColor = Color::FromArgb(40, 55, 82);

        native_panel->HeaderPadding = Padding(5, 0, 5, 0);
        native_panel->HeaderColor = Conversions::GetApplicationColor(ApplicationColor::AppColorPanelHeader, false);
        native_panel->ForeColor = Conversions::GetApplicationColor(ApplicationColor::AppColorPanelHeader, true);
        native_panel->HeaderColorFocused = Conversions::GetApplicationColor(ApplicationColor::AppColorPanelHeaderFocused, false);
        native_panel->ForeColorFocused = Conversions::GetApplicationColor(ApplicationColor::AppColorPanelHeaderFocused, true);
        break;
      }

    default:
      throw std::logic_error("Internal error: unhandled mforms panel type.");
    }

    control->AutoSize= false;
    return true;
  }
  return false;
}

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

void PanelImpl::set_title(mforms::Panel *self, const std::string &title)
{
  PanelImpl^ panel= (PanelImpl^)ObjectImpl::FromUnmanaged(self);

  if (panel != nullptr)
  {
    panel->set_title(title);
    self->set_layout_dirty(true);
  }
}

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

void PanelImpl::set_back_color(mforms::Panel *self, const std::string &color)
{
  PanelImpl^ panel= (PanelImpl^)ObjectImpl::FromUnmanaged(self);

  if (panel != nullptr)
  {
    panel->set_back_color(color);
  }
}

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

void PanelImpl::add(mforms::Panel *self, mforms::View *view)
{
  PanelImpl^ panel= (PanelImpl^)ObjectImpl::FromUnmanaged(self);

  if (panel != nullptr)
  {
    panel->add(view);
    self->set_layout_dirty(true);
  }
}

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

void PanelImpl::set_active(mforms::Panel* self, bool value)
{
  PanelImpl^ panel= (PanelImpl^)ObjectImpl::FromUnmanaged(self);

  if (panel != nullptr)
  {
    panel->set_active(value);
  }
}

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

bool PanelImpl::get_active(mforms::Panel* self)
{
  PanelImpl^ panel= (PanelImpl^)ObjectImpl::FromUnmanaged(self);

  if (panel != nullptr)
    return panel->get_active();

  return false;
}

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

void PanelImpl::remove(mforms::Panel *self, mforms::View* view)
{
  PanelImpl^ panel= (PanelImpl^)ObjectImpl::FromUnmanaged(self);

  if (panel != nullptr)
  {
    panel->remove(view);
    self->set_layout_dirty(true);
  }
}

//----------------- PanelImpl actual implementation -----------------------------------------------

void PanelImpl::set_title(const std::string &title)
{
  Control^ control = get_control<Control>();
  control->Text= CppStringToNative(title);
  control->Refresh();
}

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

void PanelImpl::set_back_color(const std::string &color)
{
  switch (type)
  {
    case mforms::TitledBoxPanel:
    case mforms::BorderedPanel: 
    case mforms::TitledGroupPanel:
      get_control<GroupBox>()->BackColor= System::Drawing::ColorTranslator::FromHtml(CppStringToNative(color));
      break;
    default:
      get_control<Panel>()->BackColor= System::Drawing::ColorTranslator::FromHtml(CppStringToNative(color));
  }
}

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

void PanelImpl::add(mforms::View *view)
{
  ViewImpl^ child= (ViewImpl^)ObjectImpl::FromUnmanaged(view);
  Control^ ctl= child->get_control<Control>();
  //ctl->Dock = DockStyle::Fill; // Fill dock style defeats the purpose of doing our own layout.

  _child= view;
  Control^ panel_ctl= get_control<Control>();
  panel_ctl->Controls->Add(ctl);
}

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

void PanelImpl::set_active(bool value)
{
  // TODO: implement as soon as the checkbox is available
}

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

bool PanelImpl::get_active()
{
  // TODO: implement as soon as the checkbox is available
  return false;
}

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

void PanelImpl::remove(mforms::View* view)
{
  ViewImpl^ child= (ViewImpl^)ObjectImpl::FromUnmanaged(view);
  Control^ ctl= child->get_control<Control>();

  Control^ panel_ctl= get_control<Control>();
  if (_child == view)
    _child= NULL;
  panel_ctl->Controls->Remove(ctl);
}

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

void PanelImpl::remove()
{
  get_control<Control>()->Controls->Clear();
}

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