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

#include "stdafx.h"

#include "base/log.h"

#include "mforms/mforms.h"
#include "wf_view.h"
#include "wf_treenodeview.h"
#include "wf_treenode.h"
#include "wf_menubar.h"

#include "ConvUtils.h"

using namespace System::Collections::Generic;

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

DEFAULT_LOG_DOMAIN(DOMAIN_MFORMS_WRAPPER)

//----------------- ColumnComparer -----------------------------------------------------------------

/**
  * The class that does the actual text comparison for sorting.
  */
private ref class ColumnComparer : public IComparer<Node^>
{
private:
  int _column;
  ManagedTreeColumnType _type;
  SortOrder _direction;
  System::Collections::CaseInsensitiveComparer^ _inner;
public:
  ColumnComparer(int column, SortOrder direction, ManagedTreeColumnType type)
  {
    _column = column;
    _direction = direction;
    _inner = gcnew System::Collections::CaseInsensitiveComparer();
    _type = type;
  }

  virtual int Compare(Node^ n1, Node^ n2)
  {
    MySQL::Forms::TreeViewNode^ node1 = dynamic_cast<MySQL::Forms::TreeViewNode^>(n1);
    MySQL::Forms::TreeViewNode^ node2 = dynamic_cast<MySQL::Forms::TreeViewNode^>(n2);

    switch (_type)
    {
    case ManagedTreeColumnType::MStringColumnType:
    case ManagedTreeColumnType::MIconStringColumnType:
      // There's an attached string for icons, which we use to compare.
      if (_direction == SortOrder::Ascending)
        return _inner->Compare(node1->Caption[_column], node2->Caption[_column]);
      else
        return _inner->Compare(node2->Caption[_column], node1->Caption[_column]);
      break;
    case ManagedTreeColumnType::MIntegerColumnType:
    case ManagedTreeColumnType::MLongIntegerColumnType:
      {
        Int64 i1 = 0, i2 = 0;
        Int64::TryParse(node1->Caption[_column], i1);
        Int64::TryParse(node2->Caption[_column], i2);
        if (_direction == SortOrder::Ascending)
          return i1 < i2 ? -1 : (i1 > i2 ? 1 : 0);
        else
          return i1 > i2 ? -1 : (i1 < i2 ? 1 : 0);
        break;
      }
    case ManagedTreeColumnType::MCheckColumnType:
      {
        bool b1 = node1->Caption[_column] == "1" ? true : false;
        bool b2 = node2->Caption[_column] == "1" ? true : false;
        if (_direction == SortOrder::Ascending)
          return b1 < b2 ? -1 : 1;
        else
          return b2 < b1 ? -1 : 1;
        break;
      }

    default:
      return 0;
    }
  }

};

//----------------- SortableTreeModel --------------------------------------------------------------

Collections::IEnumerable^ SortableTreeModel::GetChildren(TreePath ^treePath)
{
  Node^ node = FindNode(treePath);
  if (node != nullptr)
  {
    if (_comparer != nullptr)
    {
      node->Nodes->Sort(_comparer);
      /*
      ArrayList^ list = gcnew ArrayList();
      list->AddRange(node->Nodes);
      list->Sort(_comparer);
      return list;*/
    }
   return node->Nodes;
  }

  return nullptr;
}

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

/**
 * Workaround for tree manipulations not going via the model (which would trigger the
 * structure change event automatically).
 */
void SortableTreeModel::Resort()
{
  OnStructureChanged(gcnew TreePathEventArgs());
}

//----------------- AttributedNodeText -------------------------------------------------------------

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

  TreeViewNode^ node= dynamic_cast<TreeViewNode^>(args->Node->Tag);

  mforms::TreeNodeTextAttributes attributes = node->Attributes[ParentColumn->Index];
  if (!attributes.bold && !attributes.italic && !attributes.color.is_valid())
    return;

  FontStyle newStyle = FontStyle::Regular;
  if (attributes.bold)
    newStyle = newStyle | FontStyle::Bold;
  if (attributes.italic)
    newStyle = newStyle | FontStyle::Italic;
  args->Font = gcnew System::Drawing::Font(args->Font, newStyle);
}

//----------------- TreeNodeViewImpl static interface functions ----------------------------------------

bool TreeNodeViewImpl::create(mforms::TreeNodeView *self, mforms::TreeOptions options)
{
  TreeNodeViewImpl ^treeview = gcnew TreeNodeViewImpl(self, (options & mforms::TreeIndexOnTag) != 0);

  if (treeview != nullptr)
  {
    TreeViewAdv^ t= ViewImpl::create<TreeViewAdv>(self, treeview);
    t->AsyncExpanding = false;
    t->LoadOnDemand = true;
    t->Model= treeview->_model;
    t->SelectionChanged += gcnew System::EventHandler(&TreeNodeViewImpl::SelectionChanged);
    t->NodeMouseDoubleClick += gcnew System::EventHandler<TreeNodeAdvMouseEventArgs^>(&TreeNodeViewImpl::NodeActivated);
    t->UseColumns= options & mforms::TreeNoColumns ? false : true;
    t->MouseDown += gcnew MouseEventHandler(&TreeNodeViewImpl::OnMouseDown);
    t->ColumnClicked += gcnew System::EventHandler<TreeColumnEventArgs^>(treeview, &TreeNodeViewImpl::OnColumnClicked);
    t->Collapsed += gcnew System::EventHandler<TreeViewAdvEventArgs^>(treeview, &TreeNodeViewImpl::Collapsed);
    t->Expanded += gcnew System::EventHandler<TreeViewAdvEventArgs^>(treeview, &TreeNodeViewImpl::Expanded);
    if (options & mforms::TreeNoBorder) // Default is 3d border.
    {
      t->BorderStyle = BorderStyle::None;

      // Add some padding or the content will directly start at the boundaries.
      t->Padding = Padding(2);
    }

    t->ShowLines = false;
    t->ShowPlusMinus = false;

    treeview->_flat_list = (options & mforms::TreeFlatList) != 0;
    if ((options & mforms::TreeAltRowColors) != 0)
      t->BeforeDrawNode += gcnew System::EventHandler<TreeViewAdvDrawRowEventArgs^>(treeview, &TreeNodeViewImpl::OnBeforeNodeDrawing);

    return true;
  }
  return false;
}

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

int TreeNodeViewImpl::add_column(mforms::TreeNodeView *self, mforms::TreeColumnType type, const std::string &name, 
                             int initial_width, bool editable)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->add_column(type, name, initial_width, editable);
  return -1;
}

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

void TreeNodeViewImpl::end_columns(mforms::TreeNodeView *self)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->end_columns();
}

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

void TreeNodeViewImpl::clear(mforms::TreeNodeView *self)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->clear();
}

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

void TreeNodeViewImpl::set_row_height(mforms::TreeNodeView *self, int h)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->set_row_height(h);
}

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

std::list<mforms::TreeNodeRef> TreeNodeViewImpl::get_selection(mforms::TreeNodeView *self)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->get_selection();
  return std::list<mforms::TreeNodeRef>();
}

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

mforms::TreeNodeRef TreeNodeViewImpl::get_selected_node(mforms::TreeNodeView *self)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->get_selected_node();
  return mforms::TreeNodeRef();
}

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

mforms::TreeNodeRef TreeNodeViewImpl::root_node(mforms::TreeNodeView *self)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->root_node();
  return mforms::TreeNodeRef();
}


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

void TreeNodeViewImpl::set_selected(mforms::TreeNodeView *self, mforms::TreeNodeRef node, bool flag)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->set_selected(node, flag);
}


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

void TreeNodeViewImpl::clear_selection(mforms::TreeNodeView *self)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->clear_selection();
}

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

mforms::TreeSelectionMode TreeNodeViewImpl::get_selection_mode(mforms::TreeNodeView *self)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->get_selection_mode();
  return mforms::TreeSelectSingle;
}

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

void TreeNodeViewImpl::set_selection_mode(mforms::TreeNodeView *self, mforms::TreeSelectionMode mode)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->set_selection_mode(mode);
}

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

void TreeNodeViewImpl::set_allow_sorting(mforms::TreeNodeView *self, bool flag)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->allow_column_sorting(flag);
}

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

void TreeNodeViewImpl::freeze_refresh(mforms::TreeNodeView *self, bool flag)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->freeze_refresh(flag);
}

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

mforms::TreeNodeRef TreeNodeViewImpl::node_at_row(mforms::TreeNodeView *self, int row)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->node_at_row(row);
  return mforms::TreeNodeRef();
}

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

int TreeNodeViewImpl::row_for_node(mforms::TreeNodeView *self, mforms::TreeNodeRef node)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->row_for_node(node);
  return -1;
}

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

mforms::TreeNodeRef TreeNodeViewImpl::node_with_tag(mforms::TreeNodeView *self, const std::string &tag)
{
  TreeNodeViewImpl^ treeview= (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->node_with_tag(tag);
  return mforms::TreeNodeRef();
}

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

void TreeNodeViewImpl::set_column_visible(mforms::TreeNodeView *self, int column, bool flag)
{
  TreeNodeViewImpl^ treeview = (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->set_column_visible(column, flag);
}

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

bool TreeNodeViewImpl::get_column_visible(mforms::TreeNodeView *self, int column)
{
  TreeNodeViewImpl^ treeview = (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->is_column_visible(column);
  return false;
}

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

TreeNodeViewImpl::TreeNodeViewImpl(mforms::TreeNodeView *self, bool use_tag_index)
  : ViewImpl(self)
{
  _model = gcnew SortableTreeModel();
  _current_sort_column = -1;
  _can_sort_column = false;
  _freeze_count = 0;
  _current_sort_order = SortOrder::None;
  _tag_map = use_tag_index ? gcnew Dictionary<String^, TreeViewNode^>() : nullptr;
}

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

TreeNodeViewImpl::~TreeNodeViewImpl()
{
  TreeViewAdv^ treeview= get_control<TreeViewAdv>();
  treeview->HideEditor();

  for (int i = 0; i < _model->Root->Nodes->Count; ++i)
    dynamic_cast<MySQL::Forms::TreeViewNode^> (_model->Root->Nodes[i])->destroy_data_recursive();
  _model->Root->Nodes->Clear();
};

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

int TreeNodeViewImpl::add_column(mforms::TreeColumnType type, const std::string &name, int initial_width, bool editable)
{
  TreeViewAdv^ treeview= get_control<TreeViewAdv>();
  
  BindableControl^ nodeControl;
  NodeIcon^ icon = nullptr;
  switch (type)
  {
  case mforms::CheckColumnType:
    nodeControl= gcnew NodeCheckBox();
    break;
  case mforms::IntegerColumnType:
  case mforms::LongIntegerColumnType:
    {
      NodeTextBox^ box= gcnew NodeTextBox(); // Does an integer column work differently than a normal text column (editor-wise)?
      box->EditEnabled= editable;
      box->TextAlign = HorizontalAlignment::Right;
      nodeControl= box;
      break;
    }
  case mforms::IconColumnType:
    {
      icon = gcnew NodeIcon();

      NodeTextBox^ box= gcnew AttributedNodeText();
      box->EditEnabled= editable;
      box->Trimming = StringTrimming::EllipsisCharacter;
      nodeControl = box;
      break;
    }
  default: // mforms::StringColumnType
    {
      NodeTextBox^ box= gcnew AttributedNodeText();
      box->EditEnabled= editable;
      box->Trimming = StringTrimming::EllipsisCharacter;
      nodeControl= box;
    }
  };

  TreeColumn^ column= gcnew TreeColumn(CppStringToNative(name), (initial_width < 0) ? 50 : initial_width);
  treeview->Columns->Add(column);
  _column_types.Add((ManagedTreeColumnType)type); // We have a 1:1 mapping, so we can just cast.

  if (!_flat_list && treeview->Columns->Count == 1)
  {
    TriangleNodeControl^ control = gcnew TriangleNodeControl();
    control->ParentColumn= column;
    treeview->NodeControls->Add(control);
//    add_node_control(control, model_column, false, false);
  }

  if (icon != nullptr)
  {
    icon->VirtualMode= true; // Means: get value for that column via event.
    icon->ValueNeeded += gcnew System::EventHandler<NodeControlValueEventArgs^>(TreeValueNeeded);
    icon->LeftMargin= 3;
    icon->ParentColumn= column;

    treeview->NodeControls->Add(icon);
  }

  nodeControl->VirtualMode= true; // Means: get value for that column via event.
  nodeControl->ValueNeeded += gcnew System::EventHandler<NodeControlValueEventArgs^>(TreeValueNeeded);
  nodeControl->ValuePushed += gcnew System::EventHandler<NodeControlValueEventArgs^>(TreeValuePushed);
  nodeControl->LeftMargin= 3;
  nodeControl->ParentColumn= column;
  treeview->NodeControls->Add(nodeControl);

  return column->Index;
}

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

/**
 * Returns the text for the given node control (passed in as sender, representing the column) and for
 * the given node (passed in the event arguments).
 */
void TreeNodeViewImpl::TreeValueNeeded(System::Object^ sender, NodeControlValueEventArgs^ e)
{
  BindableControl^ control= (BindableControl^) sender;
  TreeNodeAdv^ treeNode= e->Node;
  TreeViewNode^ ourNode= dynamic_cast<TreeViewNode^>(treeNode->Tag);
  
  // The passed in TreeNodeAdv can be the hidden root node, so better check it.
  if (ourNode != nullptr)
  {
    String^ value= ourNode->Caption[control->ParentColumn->Index];
    if (control->GetType() == NodeCheckBox::typeid)
      e->Value = (value == "Checked") || (value == "1") ? true : false;
    else
      if (control->GetType() == NodeIcon::typeid)
        e->Value = ourNode->Icon[control->ParentColumn->Index];
      else
        e->Value= value;
  }
}

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

/**
 * Sets new text for the given node control (passed in as sender, representing the column) and for
 * the given node (passed in the event arguments).
 */
void TreeNodeViewImpl::TreeValuePushed(System::Object^ sender, NodeControlValueEventArgs^ e)
{
  BindableControl^ control= (BindableControl^) sender;
  TreeNodeAdv^ treeNode= e->Node;
  TreeViewNode^ ourNode= dynamic_cast<TreeViewNode^>(treeNode->Tag);

  TreeViewAdv^ tree= dynamic_cast<TreeViewAdv^>(ourNode->Tag);
  mforms::TreeNodeView* native_tree= ObjectImpl::get_backend_control<mforms::TreeNodeView>(tree);

  // First check if the backend allows editing that value.
  std::string new_value= NativeToCppString(e->Value->ToString());
  if (new_value == "Checked")
    new_value= "1";
  if (new_value == "Unchecked")
    new_value= "0";
  if (native_tree->cell_edited(mforms::TreeNodeRef(new TreeNodeImpl(tree, ourNode)), control->ParentColumn->Index, new_value))
  {
    // Backend says ok, so update the model.
    if (ourNode != nullptr)
      ourNode->Caption[control->ParentColumn->Index] = e->Value->ToString();
  }
}

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

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

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

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

void TreeNodeViewImpl::Expanded(System::Object^ sender, TreeViewAdvEventArgs^ e)
{
   TreeViewAdv^ tree= (TreeViewAdv^)sender;
   TreeNodeAdv^ treeNode= e->Node;

   TreeViewNode^ ourNode= dynamic_cast<TreeViewNode^>(treeNode->Tag);

  if (tree->Tag != nullptr)
  {
    mforms::TreeNodeView* t= ViewImpl::get_backend_control<mforms::TreeNodeView>(tree);
    if (t != 0)
    {
      t->expand_toggle(mforms::TreeNodeRef(new TreeNodeImpl(tree, ourNode)), true);
    }
  }
}

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

void TreeNodeViewImpl::Collapsed(System::Object^ sender, TreeViewAdvEventArgs^ e)
{
   TreeViewAdv^ tree= (TreeViewAdv^)sender;
   TreeNodeAdv^ treeNode= e->Node;

   TreeViewNode^ ourNode= dynamic_cast<TreeViewNode^>(treeNode->Tag);

  if (tree->Tag != nullptr)
  {
    mforms::TreeNodeView* t= ViewImpl::get_backend_control<mforms::TreeNodeView>(tree);
    if (t != 0)
    {
      t->expand_toggle(mforms::TreeNodeRef(new TreeNodeImpl(tree, ourNode)), false);
    }
  }
}

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

void TreeNodeViewImpl::NodeActivated(System::Object^ sender, TreeNodeAdvMouseEventArgs^ args)
{
  TreeViewAdv^ tree= (TreeViewAdv^)sender;

  TreeNodeAdv^ treeNode= args->Node;
  TreeViewNode^ ourNode= dynamic_cast<TreeViewNode^>(treeNode->Tag);

  if (tree->Tag != nullptr)
  {
    mforms::TreeNodeView* t= ViewImpl::get_backend_control<mforms::TreeNodeView>(tree);
    if (t != 0)
    {
      int row = t->row_for_node(mforms::TreeNodeRef(new TreeNodeImpl(tree, ourNode)));

      int column= args->Control->ParentColumn->Index;
      t->node_activated(mforms::TreeNodeRef(new TreeNodeImpl(tree, ourNode)), column);
    }
  }
}

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

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

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

    // update the associated context menu
    if (t && t->get_context_menu())
    {
      ToolStrip^ menu = MenuBarImpl::GetNativeControl(t->get_context_menu());
      if (menu != tree->ContextMenuStrip)
      {
        tree->ContextMenuStrip = (ContextMenuStrip^)menu;
        t->get_context_menu()->will_show();
      }
    }
    else
      tree->ContextMenuStrip = nullptr;
  }
}

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

void TreeNodeViewImpl::OnColumnClicked(System::Object^ sender, TreeColumnEventArgs^ e)
{
  if (_can_sort_column)
  {
    // Note: TreeViewAdv^ uses sort indicators wrongly. They are used as if they were arrows pointing
    //       to the greater value. However common usage is to view them as normal triangles with the
    //       tip showing the smallest value and the base side the greatest (Windows explorer and many
    //       other controls use it that way). Hence I switch the order here for the columns
    //       (though not for the comparer).
    TreeViewAdv^ tree= (TreeViewAdv^)sender;
    if (e->Column->Index != _current_sort_column)
    {
      _current_sort_column = e->Column->Index;

      // Initial sort order is always ascending.
      e->Column->SortOrder = SortOrder::Descending;
      _current_sort_order = SortOrder::Ascending;
    }
    else
    {
      if (e->Column->SortOrder == SortOrder::Ascending)
        e->Column->SortOrder = SortOrder::Descending;
      else
        e->Column->SortOrder = SortOrder::Ascending;

      // Revert the sort order for our comparer, read above why.
      _current_sort_order = (e->Column->SortOrder == SortOrder::Ascending) ? SortOrder::Descending : SortOrder::Ascending;
    }
    _model->Comparer = gcnew ColumnComparer(_current_sort_column, _current_sort_order,
      _column_types[_current_sort_column]);
  }
}

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

/**
 * Called only if alternating row colors are enabled.
 */
void TreeNodeViewImpl::OnBeforeNodeDrawing(System::Object^ sender, TreeViewAdvDrawRowEventArgs^ e)
{
  // Alternating color only for odd rows.
  if (((e->Node->Row % 2) != 0))
  {
    Graphics^ graphics = e->Context.Graphics;
    System::Drawing::Rectangle bounds = e->Context.Bounds;
    System::Drawing::Color color = System::Drawing::Color::FromArgb(237, 243, 253);
    Brush^ brush = gcnew SolidBrush(color);
    graphics->FillRectangle(brush, bounds);
    delete brush;
  }
}

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

void TreeNodeViewImpl::end_columns()
{
  TreeViewAdv^ treeview= get_control<TreeViewAdv>();
  treeview->Model = _model;
}

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

void TreeNodeViewImpl::clear()
{
  _model->Nodes->Clear();
}

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

void TreeNodeViewImpl::set_row_height(int h)
{
  TreeViewAdv^ treeview= get_control<TreeViewAdv>();
  treeview->RowHeight = h;
}

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

mforms::TreeNodeRef TreeNodeViewImpl::get_selected_node()
{
  TreeNodeAdv ^snode = get_control<TreeViewAdv>()->CurrentNode;
  if (snode == nullptr || snode->Tag == nullptr)
    return mforms::TreeNodeRef();
  return mforms::TreeNodeRef(new TreeNodeImpl(get_control<TreeViewAdv>(), (TreeViewNode^)snode->Tag));
}

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

void TreeNodeViewImpl::clear_selection()
{
  TreeViewAdv^ tree= get_control<TreeViewAdv>();
  if (tree)
    tree->ClearSelection();
}

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

std::list<mforms::TreeNodeRef> TreeNodeViewImpl::get_selection()
{
  std::list<mforms::TreeNodeRef> selection;
  TreeViewAdv^ tree = get_control<TreeViewAdv>();
  if (tree != nullptr)
  {
    ReadOnlyCollection<TreeNodeAdv^> ^sel = tree->SelectedNodes;
    if (sel != nullptr)
    {
      for (int i = 0; i < sel->Count; i++)
      {
        TreeViewNode ^node = (TreeViewNode^)sel[i]->Tag;
        selection.push_back(mforms::TreeNodeRef(new TreeNodeImpl(tree, node)));
      }
    }
  }
  return selection;
}

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

mforms::TreeSelectionMode TreeNodeViewImpl::get_selection_mode()
{
  TreeViewAdv^ tree= get_control<TreeViewAdv>();
  if (tree)
  {
    switch (tree->SelectionMode)
    {
    case Aga::Controls::Tree::TreeSelectionMode::Single:  
      return mforms::TreeSelectSingle;
    case Aga::Controls::Tree::TreeSelectionMode::Multi:
      return mforms::TreeSelectMultiple; 
    }
  }
  return mforms::TreeSelectSingle;
}

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

void TreeNodeViewImpl::set_selection_mode(mforms::TreeSelectionMode mode)
{
  TreeViewAdv^ tree= get_control<TreeViewAdv>();
  if (tree)
  {
    switch (mode)
    {  
    case mforms::TreeSelectSingle:
      tree->SelectionMode = Aga::Controls::Tree::TreeSelectionMode::Single;
      break;
    case mforms::TreeSelectMultiple:
      tree->SelectionMode = Aga::Controls::Tree::TreeSelectionMode::Multi;
      break;
    }
  }
}
//--------------------------------------------------------------------------------------------------

mforms::TreeNodeRef TreeNodeViewImpl::root_node()
{
  return mforms::TreeNodeRef(new TreeNodeImpl(get_control<TreeViewAdv>()));
}

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

static mforms::TreeNodeRef find_node_at_row(mforms::TreeNodeRef node, int &row_counter, int row)
{
  mforms::TreeNodeRef res;
  for (int i = 0, c = node->count(); i < c; i++)
  {
    mforms::TreeNodeRef child = node->get_child(i);
    if (row_counter == row)
      return child;
    row_counter++;
    if (child->is_expanded())
    {
      res = find_node_at_row(child, row_counter, row);
      if (res)
        return res;
    }
  }
  return res;
}

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

mforms::TreeNodeRef TreeNodeViewImpl::node_at_row(int row)
{
  int i = 0;
  mforms::TreeNodeRef node = find_node_at_row(root_node(), i, row);
  return node;
}

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

static int count_rows_in_node(mforms::TreeNodeRef node)
{
  if (node->is_expanded())
  {
    int count = node->count();
    for (int i = 0, c = node->count(); i < c; i++)
    {
      mforms::TreeNodeRef child(node->get_child(i));
      if (child)
        count += count_rows_in_node(child);
    }
    return count;
  }
  return 0;
}

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

int TreeNodeViewImpl::row_for_node(mforms::TreeNodeRef node)
{
  TreeNodeImpl *impl = dynamic_cast<TreeNodeImpl*>(node.ptr());
  if (impl)
  {
    mforms::TreeNodeRef parent = node->get_parent();
    int node_index = impl->node_index();
    int row = node_index;

    if (parent)
    {
      for (int i = 0; i < node_index; i++)
        row += count_rows_in_node(parent->get_child(i));
      if (parent != root_node())
        row += row_for_node(parent);
    }
    return row;
  }
  return -1;
}

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

mforms::TreeNodeRef TreeNodeViewImpl::node_with_tag(const std::string &tag)
{
  if (_tag_map != nullptr)
  {
    String^ native_tag = CppStringToNativeRaw(tag);
    TreeViewNode^ node;
    if (_tag_map->TryGetValue(native_tag, node))
      return mforms::TreeNodeRef(new TreeNodeImpl(get_control<TreeViewAdv>(), node));
    return mforms::TreeNodeRef();
  }
  throw std::logic_error("Tree node <-> tag mapping requires tree creation option TreeIndexOnTag");
}

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

void TreeNodeViewImpl::set_column_visible(int column, bool flag)
{
  TreeViewAdv^ tree = get_control<TreeViewAdv>();
  tree->Columns[column]->IsVisible = flag;
}

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

bool TreeNodeViewImpl::is_column_visible(int column)
{
  TreeViewAdv^ tree = get_control<TreeViewAdv>();
  return tree->Columns[column]->IsVisible;
}

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

/**
 * Adds, removes or changes a node <-> tag mapping (if mapping is enabled).
 */
void TreeNodeViewImpl::process_mapping(TreeViewNode^ node, const std::string &tag)
{
  if (_tag_map != nullptr)
  {
    String^ native_tag = CppStringToNativeRaw(tag);

    if (node == nullptr)
      _tag_map->Remove(native_tag);
    else
      _tag_map[native_tag] = node;
  }
}

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

void TreeNodeViewImpl::set_selected(mforms::TreeNodeRef node, bool flag)
{
  TreeNodeImpl *impl = dynamic_cast<TreeNodeImpl*>(node.ptr());
  if (impl)
  {
    TreeNodeAdv ^tna = impl->find_node_adv();
    if (tna)
      tna->IsSelected = true;
  }
}

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

void TreeNodeViewImpl::allow_column_sorting(bool flag)
{
  _can_sort_column = flag;
  if (_current_sort_column > -1)
  {
    TreeViewAdv^ tree = get_control<TreeViewAdv>();
    tree->Columns[_current_sort_column]->SortOrder = _current_sort_order;
  }
}

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

int TreeNodeViewImpl::current_sort_column()
{
  return _current_sort_column;
}

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

void TreeNodeViewImpl::freeze_refresh(bool flag)
{
  TreeViewAdv^ tree= get_control<TreeViewAdv>();
  if (flag)
    ++_freeze_count;
  else
    if (_freeze_count == 0)
      log_error("TreeNodeView: attempt to thaw an unfrozen tree");
    else
      --_freeze_count;

  if (_freeze_count == 1)
    ControlUtilities::SuspendDrawing(tree);
  else
    if (_freeze_count == 0)
    {
      ControlUtilities::ResumeDrawing(tree);
      if (is<SortableTreeModel^>(_model))
        _model->Resort();
      else
        tree->Refresh();
    }
}

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

bool TreeNodeViewImpl::is_frozen()
{
  return _freeze_count > 0;
}

//----------------- 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.
}

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