/* 
 * 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 "mforms/mforms.h"
#include "wf_view.h"
#include "wf_treenodeview.h"
#include "wf_treenode.h"
#include "wf_grttreeview.h"
#include "wf_menu.h"

#include "ConvUtils.h"

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

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

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

  virtual int Compare(System::Object^ object1, System::Object^ object2)
  {
    MySQL::Forms::TreeViewNode^ node1 = dynamic_cast<MySQL::Forms::TreeViewNode^>(object1);
    MySQL::Forms::TreeViewNode^ node2 = dynamic_cast<MySQL::Forms::TreeViewNode^>(object2);

    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->Text[_column], node2->Text[_column]);
      else
        return _inner->Compare(node2->Text[_column], node1->Text[_column]);
      break;
    case ManagedTreeColumnType::MIntegerColumnType:
    case ManagedTreeColumnType::MLongIntegerColumnType:
      {
        Int64 i1 = 0, i2 = 0;
        Int64::TryParse(node1->Text[_column], i1);
        Int64::TryParse(node2->Text[_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->Text[_column] == "1" ? true : false;
        bool b2 = node2->Text[_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)
    {
      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;

    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();
}

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

TreeNodeViewImpl::TreeNodeViewImpl(mforms::TreeNodeView *self, bool use_tag_index)
  : ViewImpl(self)
{
  _model = gcnew SortableTreeModel();
  _current_sort_column = -1;
  _can_sort_column = false;
  _current_sort_order = SortOrder::None;
  _tag_map = use_tag_index ? gcnew Dictionary<String^, Node^>() : 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->Text[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->Text[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())
    {
      ContextMenuStrip^ menu= MenuImpl::GetNativeControl(t->get_context_menu());
      if (menu != tree->ContextMenuStrip)
      {
        tree->ContextMenuStrip = menu;
        (*t->get_context_menu()->signal_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>(), (Node^)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++)
      {
        Node ^node = (Node^)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>(), _model->Root));
}

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

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;
  return find_node_at_row(root_node(), i, row);
}

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

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);
    Node^ 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");
}

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

/**
 * Adds, removes or changes a node <-> tag mapping (if mapping is enabled).
 */
void TreeNodeViewImpl::process_mapping(Node^ 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;//XXX not working
  }
}

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

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;
  }
}

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

void TreeNodeViewImpl::freeze_refresh(bool flag)
{
  TreeViewAdv^ tree= get_control<TreeViewAdv>();
  if (flag)
    ControlUtilities::SuspendDrawing(tree);
  else
  {
    ControlUtilities::ResumeDrawing(tree);
    tree->Refresh();
  }
}

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