/* 
 * 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 "wf_view.h"

using namespace Windows::Forms;
using namespace System::Drawing;

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

using namespace Aga::Controls::Tree;
using namespace ScintillaNet;

DEFAULT_LOG_DOMAIN(DOMAIN_MFORMS_WRAPPER)

//----------------- ViewImpl ----------------------------------------------------------------------

ViewImpl::ViewImpl(::mforms::View *view)
  : ObjectImpl(view)
{
  tooltip= nullptr;
}

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

ViewImpl::~ViewImpl()
{
  delete tooltip;
}

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

/**
 * Determines, depending on the type of the given control, if the layout process should use the control's
 * minimum or its preferred size. 
 * This is necessary because there is a discrepancy between what is considered the minimum size for layouting
 * and the same size for Windows controls. The latter is usually just 0. Instead the preferred size comes close
 * to what we need in the layouting process. Unfortunately, some controls just return their current size
 * (even if they are empty) or the size they need to fit their full text (text box).
 */
bool ViewImpl::use_min_width_for_layout(Control^ control)
{
  TextBox^ text= dynamic_cast<TextBox^>(control);
  bool needsMinWidth= (text != nullptr) && (text->Multiline);
  if (!needsMinWidth)
  {
    System::Type^ type = control->GetType();
    needsMinWidth= (type == TabControl::typeid) || (type == Panel::typeid) || (type == ComboBox::typeid)
      || (type == TreeViewAdv::typeid) || (type == ListBox::typeid) || (type == TextBox::typeid)
      || (type == SplitContainer::typeid) || (type == Scintilla::typeid)
      
      // TODO: investigate if we can remove this and override GetPreferredSize in FlatTabControl instead.
      || (type == FlatTabControl::typeid);
  }

  return needsMinWidth;
}

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

bool ViewImpl::use_min_height_for_layout(Control^ control)
{
  TextBox^ text= dynamic_cast<TextBox^>(control);
  bool needsMinHeight= (text != nullptr) && (text->Multiline);
  if (!needsMinHeight)
  {
    System::Type^ type = control->GetType();
    needsMinHeight= (type == TabControl::typeid) || (type == Panel::typeid) || (type == ListBox::typeid)
      || (type == TreeViewAdv::typeid) || (type == SplitContainer::typeid) || (type == Scintilla::typeid)

    // TODO: investigate if we can remove this and override GetPreferredSize in FlatTabControl instead.
    || (type == FlatTabControl::typeid);
  }

  return needsMinHeight;
}

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

/**
 * Removes the given mode from the auto resize settings of the given control.
 */
void ViewImpl::remove_auto_resize(Control^ control, mforms::AutoResizeMode mode)
{
  // Get backend object for the control, which allows to set individual auto resize modes.
  mforms::View* view= ViewImpl::get_backend_control<mforms::View>(control);
  switch (mode)
  {
    case mforms::ResizeBoth:
      view->set_resize_mode(mforms::ResizeNone);
      break;
    case mforms::ResizeHorizontal:
      if (view->get_resize_mode() == mforms::ResizeBoth)
        view->set_resize_mode(mforms::ResizeVertical);
      else
        view->set_resize_mode(mforms::ResizeNone);
      break;
    case mforms::ResizeVertical:
      if (view->get_resize_mode() == mforms::ResizeBoth)
        view->set_resize_mode(mforms::ResizeHorizontal);
      else
        view->set_resize_mode(mforms::ResizeNone);
      break;
  }
}

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

/**
 * Returns the current resize mode set for the associated back end object of control.
 */
mforms::AutoResizeMode ViewImpl::get_auto_resize(Control^ control)
{
  mforms::View* view= ViewImpl::get_backend_control<mforms::View>(control);
  return view->get_resize_mode();
}

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

/**
 * Determines if the given control can be resized in horizontal direction.
 */
bool ViewImpl::can_auto_resize_horizontally(Control^ control)
{
  mforms::View* view= ViewImpl::get_backend_control<mforms::View>(control);
  mforms::AutoResizeMode mode= view->get_resize_mode();

  return (mode == mforms::ResizeHorizontal) || (mode == mforms::ResizeBoth);
}

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

/**
 * Determines if the given control can be resized in vertical direction.
 */
bool ViewImpl::can_auto_resize_vertically(Control^ control)
{
  mforms::View* view= ViewImpl::get_backend_control<mforms::View>(control);
  mforms::AutoResizeMode mode= view->get_resize_mode();

  return (mode == mforms::ResizeVertical) || (mode == mforms::ResizeBoth);
}

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

/**
 * Enables resizing of the control in both directions.
 */
void ViewImpl::set_full_auto_resize(Control^ control)
{
  ViewImpl::get_backend_control<mforms::View>(control)->set_resize_mode(mforms::ResizeBoth);
}

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

/**
 * Sets resize mode whatever the caller specified.
 */
void ViewImpl::set_auto_resize(Control^ control, mforms::AutoResizeMode mode)
{
  ViewImpl::get_backend_control<mforms::View>(control)->set_resize_mode(mode);
}

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

bool ViewImpl::is_layout_dirty(Control^ control)
{
  mforms::View* view= ViewImpl::get_backend_control<mforms::View>(control);
  return view->is_layout_dirty();
}

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

void ViewImpl::set_layout_dirty(Control^ control, bool value)
{
  mforms::View* view= ViewImpl::get_backend_control<mforms::View>(control);
  view->set_layout_dirty(value);
}

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

void ViewImpl::destroy(::mforms::View *self)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  delete view;
}

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

void ViewImpl::show(::mforms::View *self, bool show)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);

  if (view != nullptr)
  {
    Control^ control= view->get_control<Control>();
    control->Visible= show;

    // Unfortunately, we cannot optimize handling here. Control::Visible not only returns the
    // visibility of itself but its entire view hierarchy. So we cannot distinct between the two
    // cases if the control itself is hidden or one of its parents. Setting Control::Visible
    // does the right job, though.
    //if (control->Visible != show)
    if (control->IsHandleCreated && self->get_parent() != NULL)
      self->get_parent()->set_layout_dirty(true);
  };
}

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

int ViewImpl::get_width(::mforms::View *self)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    return view->get_control<Control>()->Width;
  return 0;
}

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

int ViewImpl::get_height(::mforms::View *self)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    return view->get_control<Control>()->Height;
  return 0;
}

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

int ViewImpl::get_preferred_width(::mforms::View *self)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
  {
    return view->get_control<Control>()->PreferredSize.Width;
  }
  return 0;
}

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

int ViewImpl::get_preferred_height(::mforms::View *self)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
  {
    return view->get_control<Control>()->PreferredSize.Height;
  }
  return 0;
}

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

int ViewImpl::get_x(::mforms::View *self)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
  {
    return view->get_control<Control>()->Left;
  }
  return 0;
}

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

int ViewImpl::get_y(::mforms::View *self)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
  {
    return view->get_control<Control>()->Top;
  }
  return 0;
}

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

void ViewImpl::set_size(::mforms::View *self, int w, int h)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    view->set_size(w, h);
}

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

void ViewImpl::set_padding(::mforms::View *self, int left, int top, int right, int bottom)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
  {
    self->set_layout_dirty(true);
    view->set_padding(left, top, right, bottom);
  }
}

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

void ViewImpl::set_position(::mforms::View *self, int x, int y)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
  {
    if (self->get_parent() != NULL)
      self->get_parent()->set_layout_dirty(true);
    view->set_position(x, y);
  }
}

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

void ViewImpl::client_to_screen(::mforms::View *self, int& x, int& y)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
  {
    System::Drawing::Point location= System::Drawing::Point(x, y);
    location= view->get_control<Control>()->PointToScreen(location);
    x = location.X;
    y = location.Y;
  }
}

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

ViewImpl^ ViewImpl::get_parent()
{
  mforms::View* view= get_backend_control<mforms::View>(get_control<Control>());
  if (view != NULL && view->get_parent() != NULL)
    return (ViewImpl^) ObjectImpl::FromUnmanaged(view->get_parent());

  return nullptr;
}

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

void ViewImpl::relayout()
{
  Control^ control= get_control<Control>();
  if (control->InvokeRequired)
    control->BeginInvoke(gcnew MethodInvoker(this, &ViewImpl::relayout));
  else
    control->PerformLayout();
}

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

void ViewImpl::set_enabled(::mforms::View *self, bool flag)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    view->get_control<Control>()->Enabled= flag;
}

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

bool ViewImpl::is_enabled(::mforms::View *self)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    return view->get_control<Control>()->Enabled;
  return false;
}

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

View *ViewImpl::find_subview(::mforms::View *self, std::string &name)
{
  return 0;
}

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

void ViewImpl::set_name(::mforms::View *self, const std::string& text)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    view->get_control<Control>()->Name = CppStringToNative(text);
}

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

void ViewImpl::relayout(::mforms::View *self)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    view->relayout();
}

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

void ViewImpl::set_needs_repaint(::mforms::View *self)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    view->get_control<Control>()->Refresh();
}

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

void ViewImpl::suspend_layout(::mforms::View *self, bool flag)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
  {
    if (flag)
      view->get_control<Control>()->SuspendLayout();
    else
    {
      view->get_control<Control>()->ResumeLayout();
      if (self->is_layout_dirty())
        view->get_control<Control>()->PerformLayout();
    }
  }
}

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

void ViewImpl::set_front_color(mforms::View *self, const std::string &color)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    view->set_front_color(CppStringToNative(color));
}

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

std::string ViewImpl::get_front_color(mforms::View *self)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    return NativeToCppString(view->get_front_color());
  return "#ffffff";
}

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

void ViewImpl::set_back_color(mforms::View *self, const std::string &color)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    view->set_back_color(CppStringToNative(color));
}

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

std::string ViewImpl::get_back_color(mforms::View *self)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    return NativeToCppString(view->get_back_color());
  return "#000000";
}

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

void ViewImpl::set_back_image(mforms::View *self, const std::string &path, mforms::ImageLayout layout)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    view->set_back_image(CppStringToNative(path), layout);
}


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

void ViewImpl::set_tooltip(::mforms::View *self, const std::string& text)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    view->set_tooltip(text);
}

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

void ViewImpl::set_font(::mforms::View *self, const std::string& fontDescription)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    view->set_font(fontDescription);
}

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

bool ViewImpl::is_shown(::mforms::View *self)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    return view->get_control<Control>()->Visible;
  return false;
}

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

void ViewImpl::initialize()
{
  Control^ control= get_control<Control>();

  // Can be null, e.g. for non-control objects like dialogs.
  if (control != nullptr)
    control->Resize += gcnew EventHandler(this, &ViewImpl::OnResize);
}

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

void ViewImpl::set_size(int width, int height)
{
  Control^ control= get_control<Control>();
  Size newSize= control->Size;
  if (width >= 0)
    newSize.Width= width;
  if (height >= 0)
    newSize.Height= height;
  
  control->MinimumSize = newSize; // TODO: wrong place for this, when we restore loaded settings we don't want to set a min size.
                                  //       Implement an own set_min_size function.
  control->Size = newSize;
}

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

void ViewImpl::set_padding(int left, int top, int right, int bottom)
{
  Control^ control= get_control<Control>();
  control->Padding = Windows::Forms::Padding(left, top, right, bottom);
}

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

void ViewImpl::set_position(int x, int y)
{
  get_control<Control>()->Location = Point(x, y);
}

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

void ViewImpl::set_tooltip(const std::string& text)
{
  if (tooltip == nullptr)
  {
    tooltip= gcnew ToolTip();
    tooltip->AutoPopDelay = 10000; // 10 sec. show time
    tooltip->InitialDelay = 500;   // 500 ms before showing the tooltip if none was visible before.
    tooltip->ReshowDelay = 250;    // 250 ms before showing the tooltip if another one was active.
    tooltip->ShowAlways = true;    // Show tooltip even if the control is not active.
  }
  tooltip->SetToolTip(get_control<Control>(), CppStringToNative(text));
}

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

void ViewImpl::set_font(const std::string& fontDescription)
{
  std::string font;
  int size;
  bool bold;
  bool italic;
  base::parse_font_description(fontDescription, font, size, bold, italic);

  FontStyle style = FontStyle::Regular;
  if (bold)
    style = (FontStyle) (style | FontStyle::Bold);
  if (italic)
    style = (FontStyle) (style | FontStyle::Italic);

  try
  {
    get_control<Control>()->Font = ControlUtilities::GetFont(CppStringToNativeRaw(font),
      (float) size, style);
  }
  catch (System::ArgumentException^ e)
  {
    // Argument exception pops up when the system cannot find the Regular font style (corrupt font).
    log_error("ViewImpl::set_font failed. %s\n", e->Message);
  }
}

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

void ViewImpl::set_front_color(String ^color)
{
  get_control<Control>()->ForeColor= System::Drawing::ColorTranslator::FromHtml(color);
}

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

String^ ViewImpl::get_front_color()
{
  return System::Drawing::ColorTranslator::ToHtml(get_control<Control>()->ForeColor);
}

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

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

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

String^ ViewImpl::get_back_color()
{
  return System::Drawing::ColorTranslator::ToHtml(get_control<Control>()->BackColor);
}

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

void ViewImpl::set_back_image(String ^path, mforms::ImageLayout layout)
{
  Windows::Forms::ImageLayout native_layout;
  switch (layout)
  {
    case mforms::LayoutTile:
      native_layout= Windows::Forms::ImageLayout::Tile;
      break;
    case mforms::LayoutCenter:
      native_layout= Windows::Forms::ImageLayout::Center;
      break;
    case mforms::LayoutStretch:
      native_layout= Windows::Forms::ImageLayout::Stretch;
      break;
    case mforms::LayoutZoom:
      native_layout= Windows::Forms::ImageLayout::Zoom;
      break;
    default:
        native_layout= Windows::Forms::ImageLayout::None;
  }

  // An exception is raised if the image cannot be found.
  Image^ image= Image::FromFile(path, true);
  get_control<Control>()->BackgroundImage= image;
  get_control<Control>()->BackgroundImageLayout= native_layout;
}

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

/**
 * Resizes the given control according to its dock state. Sizes of the control which were not changed
 * are also reset in the given Size structure to allow for proper child layouting.
 */
void ViewImpl::resize_with_docking(Control^ control, System::Drawing::Size& size)
{
  // If the control is docked somewhere resizing must be restricted not to destroy the docked
  // appearance.
  // Do not resize if the control has the fill dock style. In that case it must stay as it is.
  if (control->Dock == DockStyle::Fill)
    return;

  if (control->Dock == DockStyle::Left || control->Dock == DockStyle::Right)
    size.Height= control->Size.Height;
  else
    if (control->Dock == DockStyle::Top || control->Dock == DockStyle::Bottom)
      size.Width= control->Size.Width;

  control->Size= size;
  set_layout_dirty(control, true);
}

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

/**
 * Removes auto resizing flags if a control is limited in resizing by docking.
 */
void ViewImpl::adjust_auto_resize_from_docking(Control^ control)
{
  if (control->Dock == DockStyle::Fill)
    set_auto_resize(control, mforms::ResizeNone);
  else
    if (control->Dock == DockStyle::Left || control->Dock == DockStyle::Right)
      remove_auto_resize(control, mforms::ResizeHorizontal);
    else
      if (control->Dock == DockStyle::Top || control->Dock == DockStyle::Bottom)
        remove_auto_resize(control, mforms::ResizeVertical);
}

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

void ViewImpl::flush_events(::mforms::View *)
{
  Application::DoEvents();
}

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

void ViewImpl::OnResize(System::Object ^sender, EventArgs ^e)
{
  Control^ control = (Control^)sender;

  if (control->Tag != nullptr)
  {
    mforms::View* view = ViewImpl::get_backend_control<mforms::View>(control);
    if (!view->is_destroying())
      (*view->signal_resized())();
  }
}

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