/* 
 * Copyright (c) 2009, 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
 */
/**
 * Implementation of the GRT treeview wrapper for the mforms library. We use a third-party tree view control
 * here (TreeViewAdv) which supports columns.
 */

#include "stdafx.h"

#include "mforms/mforms.h"
#include "wf_view.h"
#include "wf_grttreeview.h"
#include "wf_menu.h"

#include "grt/tree_model.h"
#include "ConvUtils.h"

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

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

using namespace mforms;

//----------------- SwitchNodeIcon -----------------------------------------------------------------

/**
 * Triggered when a node icon is double clicked. Used to trigger GRT row activation (which in turn
 * triggers other actions).
 */
void SwitchNodeIcon::MouseDoubleClick(TreeNodeAdvMouseEventArgs^ args)
{
  TreeViewAdv^ tree= args->Control->Parent;
  SimpleGrtTreeModel^ model= (SimpleGrtTreeModel^) tree->Model;
  GRTTreeView* be_tree= ViewImpl::get_backend_control<GRTTreeView>(tree);
  if (be_tree != NULL)
  {
    ::bec::NodeId* nodeId= ((GrtTreeNode^) args->Node->Tag)->NodeId->get_unmanaged_object();
    be_tree->row_activate_callback(*nodeId, model->GetColumnIndex(args->Control));
    args->Handled= true; // Prevent this node from collapsing/expanding.
  }
}

//----------------- TriangleNodeControl ------------------------------------------------------------

TriangleNodeControl::TriangleNodeControl()
{
  _expanded_icon = gcnew Bitmap("images/ui/tree_expanded.png", true);
  _collapsed_icon = gcnew Bitmap("images/ui/tree_collapsed.png", true);
}

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

Size TriangleNodeControl::MeasureSize(TreeNodeAdv^ node, DrawContext context)
{
  return _expanded_icon->Size;
}

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

void TriangleNodeControl::Draw(TreeNodeAdv^ node, DrawContext context)
{
  if (node->CanExpand)
  {
    System::Drawing::Rectangle r = context.Bounds;
    Image^ img;
    if (node->IsExpanded)
      img = _expanded_icon;
    else
      img = _collapsed_icon;
    int dy = (int) Math::Round((float) (r.Height - img->Height) / 2);
    context.Graphics->DrawImageUnscaled(img, System::Drawing::Point(r.X, r.Y + dy));
  }
}

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

void TriangleNodeControl::MouseDown(TreeNodeAdvMouseEventArgs^ args)
{
  if (args->Button == MouseButtons::Left)
  {
    args->Handled = true;
    if (args->Node->CanExpand)
      args->Node->IsExpanded = !args->Node->IsExpanded;
  }
}

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

void TriangleNodeControl::MouseDoubleClick(TreeNodeAdvMouseEventArgs^ args)
{
  args->Handled = true; // Suppress expand/collapse when double clicking.
}

//----------------- SwitchNodeText -----------------------------------------------------------------

void SwitchNodeText::MouseDoubleClick(TreeNodeAdvMouseEventArgs^ args)
{
  TreeViewAdv^ tree= args->Control->Parent;
  SimpleGrtTreeModel^ model= (SimpleGrtTreeModel^) tree->Model;
  GRTTreeView* be_tree= ViewImpl::get_backend_control<GRTTreeView>(tree);
  if (be_tree != NULL)
  {
    // XXX hack to select between old and new dbl-click behaviour. This should be fixed
    // so that the sidebar schema tree will add an activate callback to row_activate_callback
    // and this code here should call be_tree->row_activate_callback()
    if (tree->ShowPlusMinus)
    {
      ::bec::NodeId* nodeId= ((GrtTreeNode^) args->Node->Tag)->NodeId->get_unmanaged_object();
      be_tree->row_activate_callback(*nodeId, model->GetColumnIndex(args->Control));
    }
    else
      model->GrtTree->activate_node(((GrtTreeNode^) args->Node->Tag)->NodeId);
    args->Handled= true; // Prevent this node from collapsing/expanding.
  }
}

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

void SwitchNodeText::OnDrawText(DrawEventArgs^ args)
{
  NodeTextBox::OnDrawText(args);

  TreeViewAdv^ tree= Parent;
  SimpleGrtTreeModel^ model= (SimpleGrtTreeModel^) tree->Model;
  bec::NodeId* nodeId= ((GrtTreeNode^) args->Node->Tag)->NodeId->get_unmanaged_object();
  if (model->GrtTree->get_unmanaged_object()->is_highlighted(*nodeId))
    args->Font = gcnew System::Drawing::Font(args->Font, FontStyle::Bold);
}

//----------------- GRTTreeViewImpl static interface functions ----------------------------------------

bool GRTTreeViewImpl::create(GRTTreeView *self, TreeOptions options)
{
  GRTTreeViewImpl ^treeview= gcnew GRTTreeViewImpl(self);

  if (treeview != nullptr)
  {
    TreeViewAdv^ t= ViewImpl::create<TreeViewAdv>(self, treeview);
    t->LoadOnDemand= true;
    t->AutoRowHeight= true;
    t->AsyncExpanding = false;
    t->BackColor = SystemColors::Window;
    t->FullRowSelect = true;
    t->GridColor = SystemColors::Control;
    t->Indent = 16;
    t->LineColor = SystemColors::ControlDark;
    t->LoadOnDemand = true;
    t->Name = "GRTTrreeview";
    t->ShowNodeToolTips = true;
    t->HideSelection = true;
    t->Font = ControlUtilities::GetFont("Segoe UI", 9);

    if (options & ::mforms::TreeSidebar)
    {
      t->ShowPlusMinus = false; // We use our own expand/collapse icons.
      t->ShowLines = false;     // Lines don't fit our expand/collapse icons.
    }
    t->UseColumns= options & ::mforms::TreeNoColumns ? false : true;
    if (options & ::mforms::TreeNoBorder) // Default is 3d border.
      t->BorderStyle = BorderStyle::None;

    t->MouseDown += gcnew MouseEventHandler(&GRTTreeViewImpl::OnMouseDown);
    t->SelectionChanged += gcnew System::EventHandler(&GRTTreeViewImpl::SelectionChanged);

    return true;
  }
  return false;
}

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

void GRTTreeViewImpl::set_model(GRTTreeView *self, bec::TreeModel *model)
{
  GRTTreeViewImpl^ treeview= (GRTTreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->set_model(model);
}

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

int GRTTreeViewImpl::add_column(GRTTreeView *self, GRTTreeColumnType type, int model_column, 
                                const std::string &name)
{
  GRTTreeViewImpl^ treeview= (GRTTreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->add_column(type, model_column, CppStringToNative(name), false);
  return -1;
}

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

int GRTTreeViewImpl::add_column_editable(GRTTreeView *self, GRTTreeColumnType type, int model_column, 
                                const std::string &name)
{
  GRTTreeViewImpl^ treeview= (GRTTreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->add_column(type, model_column, CppStringToNative(name), true);
  return -1;
}

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

void GRTTreeViewImpl::refresh(mforms::GRTTreeView* self, const bec::NodeId &node)
{
  GRTTreeViewImpl^ treeview= (GRTTreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
  {
    if (node.is_valid())
    {
      TreeViewAdv^ t= treeview->get_control<TreeViewAdv>();
      for each (TreeNodeAdv^ current_node in t->AllNodes)
        if ((*((GrtTreeNode^) current_node->Tag)->NodeId->get_unmanaged_object()) == node)
        {
          treeview->_model->RefreshModel(t->GetPath(current_node));
          return;
        }
    }
    if (treeview->_model != nullptr)
      treeview->_model->RefreshModel();
  }
}

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

void GRTTreeViewImpl::row_count_changed(GRTTreeView *self, const bec::NodeId& node, int old_count)
{
  refresh(self, node);
}

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

void GRTTreeViewImpl::set_column_width(mforms::GRTTreeView *self, int column, int width)
{
  GRTTreeViewImpl^ treeview= (GRTTreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
  {
    TreeViewAdv^ t= treeview->get_control<TreeViewAdv>();
    t->Columns[column]->Width= width;
  }
}

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

int GRTTreeViewImpl::get_selection(mforms::GRTTreeView *self, std::vector<bec::NodeId> &nodes)
{
  GRTTreeViewImpl^ treeview= (GRTTreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  nodes.clear();
  if (treeview != nullptr)
  {
    TreeViewAdv^ t= treeview->get_control<TreeViewAdv>();

    if (t->SelectedNodes != nullptr)
    {
      for each (TreeNodeAdv^ node in t->SelectedNodes)
        nodes.push_back(*((GrtTreeNode^) node->Tag)->NodeId->get_unmanaged_object());
      return nodes.size();
    }
  }
  return 0;
}

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

/**
 * Sets the given node to the specified expand state. 
 * When expanding, the entire path to that node is expanded
 * When collapsing, only the target node is collapsed
 */
void GRTTreeViewImpl::set_expanded(mforms::GRTTreeView* self, const bec::NodeId& node, bool expanded)
{
  if (node.depth() < 1)
    return;

  GRTTreeViewImpl^ treeview= (GRTTreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
  {
    TreeViewAdv^ t= treeview->get_control<TreeViewAdv>();
    TreeNodeAdv^ walker = t->Root;
    int level = 0;
    do
    {
      if (walker->Children->Count <= node[level])
        break;

      walker = walker->Children[node[level++]];

      if (expanded)
        walker->Expand();
      else if (node.depth() == level)
        walker->Collapse();
    } while (node.depth() > level);
    
  }
}

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

void GRTTreeViewImpl::enable_asynch_loading(mforms::GRTTreeView* self, bool enabled)
{
  GRTTreeViewImpl^ treeview= (GRTTreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
  {
    TreeViewAdv^ t= treeview->get_control<TreeViewAdv>();
    t->AsyncExpanding = enabled;
  }
}


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

bool GRTTreeViewImpl::get_selected_node(GRTTreeView *self, bec::NodeId &node)
{
  GRTTreeViewImpl^ treeview= (GRTTreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
  {
    TreeViewAdv^ t= treeview->get_control<TreeViewAdv>();

    if (t->SelectedNode != nullptr)
    {
      node= *((GrtTreeNode^) t->SelectedNode->Tag)->NodeId->get_unmanaged_object();
      return true;
    }
  }
  return false;
}

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

void GRTTreeViewImpl::set_allow_multi_selection(GRTTreeView *self, bool flag)
{
  GRTTreeViewImpl^ treeview= (GRTTreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
  {
    TreeViewAdv^ t= treeview->get_control<TreeViewAdv>();
    t->SelectionMode= flag ? Aga::Controls::Tree::TreeSelectionMode::Multi : Aga::Controls::Tree::TreeSelectionMode::Single;
  }
}

//----------------- Actual implementation ----------------------------------------------------------

GRTTreeViewImpl::GRTTreeViewImpl(GRTTreeView *self)
: ViewImpl(self)
{
  _model= nullptr;
}

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

GRTTreeViewImpl::~GRTTreeViewImpl()
{
  get_control<TreeViewAdv>()->AsyncExpanding = false;
  set_model(nullptr);
}

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

void GRTTreeViewImpl::set_model(bec::TreeModel* model)
{
  if (_tree_model == nullptr)
  {
    if (model == NULL)
      return;
  }

  TreeViewAdv^ t= get_control<TreeViewAdv>();
  bool wasAsyncExpanding = t->AsyncExpanding;
  t->AsyncExpanding = false; // Switch off async expanding to avoid creating background tasks when switching models.
  if (_model != nullptr)
  {
    _model->DetachEvents();
    _model= nullptr;
  }

  if (_tree_model != nullptr)
  {
    t->Model= nullptr;
    _tree_model= nullptr;
  }

  if (model != nullptr)
  {
    _tree_model= gcnew MySQL::Grt::TreeModel(model);
    _model= gcnew SimpleGrtTreeModel(t, _tree_model, false);

    // Before setting the new model in the tree control see if we have any columns cached.
    // If so then they were created before the model was set and could not yet be finished.
    // Do the final step now.
    for each (ColumnEntry^ entry in _columns)
    {
      if (entry->isIcon)
        _model->AddColumn((NodeIcon^) (entry->nodeControl), entry->column);
      else
        _model->AddColumn(entry->nodeControl, entry->column, entry->editable);
    }

    // Finally set the model.
    t->Model= _model;
  }

  t->AsyncExpanding = wasAsyncExpanding;
}

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

/**
 * This method adds a new column control to either the tree or the internal cache.
 *
 * @param control The node control used for this column.
 * @param column The column index for this column.
 * @param editable Is the column editable?
 * @param isIcon Is this an icon control? (needs a slightly different handling)
 */
void GRTTreeViewImpl::add_node_control(NodeControl^ control, int column, bool editable, bool isIcon)
{
  if (_model == nullptr)
    _columns.Add(gcnew ColumnEntry(control, column, editable, isIcon));
  else
    if (isIcon)
      _model->AddColumn((NodeIcon^) control, column);
    else
      _model->AddColumn(control, column, editable);
}

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

int GRTTreeViewImpl::add_column(GRTTreeColumnType type, int model_column, String ^name, bool editable)
{
  TreeViewAdv^ treeview= get_control<TreeViewAdv>();
  
  TreeColumn^ column= gcnew TreeColumn(name, 150);
  treeview->Columns->Add(column);

  // If this is the main column then add our expand node control first.
  if (model_column == 0 && !treeview->ShowPlusMinus)
  {
    TriangleNodeControl^ control = gcnew TriangleNodeControl();
    control->ParentColumn= column;
    treeview->NodeControls->Add(control);
    add_node_control(control, model_column, false, false);
  }

  BindableControl^ control;
  bool isIcon= false;
  switch (type)
  {
  case StringGRTColumnType:
    {
      if (editable)
        control= gcnew NodeTextBox();
      else
        control = gcnew SwitchNodeText();
      control->LeftMargin= 4;
      break;
    }
  case IconStringGRTColumnType:
    {
      // This type need two node controls (icon and text).
      control= gcnew NodeIcon();
      control->LeftMargin = 4;
      control->ParentColumn= column;
      treeview->NodeControls->Add(control);
      add_node_control(control, model_column, editable, true);

      if (editable)
        control= gcnew NodeTextBox();
      else
        control = gcnew SwitchNodeText();
      control->LeftMargin= 4;
      break;
    }
  case IntegerGRTColumnType:
    {
      control= gcnew NodeTextBox();
      control->LeftMargin= 3;
      break;
    }
  case CheckGRTColumnType:
    {
      control= gcnew NodeCheckBox();
      control->LeftMargin= 4;
    }
    break;

  default: // IconGRTColumnType:
    {
      control= gcnew SwitchNodeIcon();
      isIcon= true;
      break;
    }
  };

  control->LeftMargin = 4;
  control->ParentColumn= column;
  treeview->NodeControls->Add(control);

  add_node_control(control, model_column, editable, isIcon);

  return column->Index;
}

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

void GRTTreeViewImpl::SelectionChanged(System::Object^ sender, EventArgs^ e)
{
  TreeViewAdv^ tree= (TreeViewAdv^)sender;

  if (tree->Tag != nullptr)
  {
    GRTTreeView* t= ViewImpl::get_backend_control<GRTTreeView>(tree);
    if (t != 0)
      t->changed();
  }
}

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

void GRTTreeViewImpl::OnMouseDown(System::Object^ sender, MouseEventArgs^ e)
{
  TreeViewAdv^ tree= (TreeViewAdv^)sender;

  if (tree->Tag != nullptr && e->Button == MouseButtons::Right)
  {
    GRTTreeView* t= ViewImpl::get_backend_control<GRTTreeView>(tree);

    // update the associated context menu
    if (t && t->get_context_menu())
    {
      tree->ContextMenuStrip = MenuImpl::GetNativeControl(t->get_context_menu());
      (*t->get_context_menu()->signal_will_show())();
    }
    else
      tree->ContextMenuStrip = nullptr;
  }
}

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