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

#include "workbench/wb_context.h" // Needed for AppImpl.
#include "wf_box.h"
#include "wf_appview.h"
#include "wf_app.h"

#include "base/log.h"
#include "base/string_utilities.h"

#include "ConvUtils.h"

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

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

using namespace Aga::Controls::Tree;

DEFAULT_LOG_DOMAIN(DOMAIN_MFORMS_WRAPPER)

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

ViewImpl::ViewImpl(mforms::View *view)
  : ObjectImpl(view)
{
  tooltip = nullptr;
  backgroundImage = 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 == ScintillaControl::typeid)
      || (type == Windows::Forms::WebBrowser::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 == ScintillaControl::typeid)
      || (type == Windows::Forms::WebBrowser::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::Alignment align)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
    view->set_back_image(CppStringToNative(path), align);
}


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

void ViewImpl::focus(mforms::View *self)
{
  ViewImpl^ view= (ViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (view != nullptr)
  {
    Control^ control = view->get_control();
    if (control->CanFocus)
      control->Focus();
  }
}

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

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

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

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)
{
  if (color == "")
    get_control<Control>()->BackColor = Color::Transparent;
  else
    get_control<Control>()->BackColor = System::Drawing::ColorTranslator::FromHtml(color);
}

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

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

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

void ViewImpl::set_back_image(String ^path, mforms::Alignment layout)
{
  path = AppImpl::get_image_path(path);
  if (File::Exists(path))
  {
    backgroundImage = Image::FromFile(path, true);
    backgroundImageAlignment = 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::Resize(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())();
  }
}

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

/**
 * Draws the background image with the earlier set layout, if there's one.
 */
void ViewImpl::DrawBackground(PaintEventArgs ^args)
{
  if (backgroundImage != nullptr)
  {
    int left = 0;
    int top = 0;
    Control ^control = get_control();

    // Horizontal alignment.
    switch (backgroundImageAlignment)
    {
    case mforms::BottomCenter:
    case mforms::MiddleCenter:
    case mforms::TopCenter:
      left = (control->Width - backgroundImage->Width) / 2;
      break;

    case mforms::BottomRight:
    case mforms::MiddleRight:
    case mforms::TopRight:
      left = control->Width - backgroundImage->Width;
      break;
    }

    // Vertical alignment.
    switch (backgroundImageAlignment)
    {
    case mforms::MiddleLeft:
    case mforms::MiddleCenter:
    case mforms::MiddleRight:
      top = (control->Height - backgroundImage->Height) / 2;
      break;

    case mforms::BottomLeft:
    case mforms::BottomCenter:
    case mforms::BottomRight:
      top = control->Height - backgroundImage->Height;
      break;
    }

    args->Graphics->DrawImage(backgroundImage, Point(left, top));
  }
}

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