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

#include "stdafx.h"
#include "wf_treeview.h"
#include "wf_menu.h"
#include "wf_app.h"

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

//----------------- TreeNode -----------------------------------------------------------------------

MySQL::Forms::TreeNode::TreeNode(TreeViewAdv^ tree)
  : _tree(tree)
{
}

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

ColumnComparer::ColumnComparer(int column, SortOrder direction)
{
  _column = column;
  _direction = direction;
  _inner = gcnew CaseInsensitiveComparer();
}

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

int ColumnComparer::Compare(System::Object^ object1, System::Object^ object2)
{
  TreeNode^ node1 = dynamic_cast<TreeNode^>(object1);
  TreeNode^ node2 = dynamic_cast<TreeNode^>(object2);
  if (_direction == SortOrder::Ascending)
    return _inner->Compare(node1->Text[_column], node2->Text[_column]);
  else
    return _inner->Compare(node2->Text[_column], node1->Text[_column]);
}

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

/**
 * Does a depth sort of the given nodes.
 */
void SortableTreeModel::SortNodes(Collection<Node^>^ nodes)
{
  // Since the original tree model uses a Collection class we have to do two copies of all nodes
  // because a Collection is not itself sortable.
  ArrayList^ list = gcnew ArrayList();
  list->AddRange(nodes);
  list->Sort(Comparer);
  nodes->Clear();
  for each (Object^ obj in list)
    nodes->Add((Node^) obj);

  // Sort all child node lists.
  for each (Node^ node in nodes)
    SortNodes(node->Nodes);
}

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

void SortableTreeModel::Sort()
{
  if (_comparer != nullptr)
  {
    SortNodes(Nodes);
    OnStructureChanged(gcnew TreePathEventArgs(TreePath::Empty));
  }
}

//----------------- TreeViewImpl static interface functions ----------------------------------------

bool TreeViewImpl::create(::mforms::TreeView *self, ::mforms::TreeOptions options)
{
  TreeViewImpl ^treeview= gcnew TreeViewImpl(self);

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

    // Currently the tree is rather used as list, so we neither need tree lines nor expand buttons.
    t->ShowLines = false;
    t->ShowPlusMinus = false;
    return true;
  }
  return false;
}

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

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

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

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

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

void TreeViewImpl::clear_rows(::mforms::TreeView *self)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->clear_rows();
}

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

void TreeViewImpl::delete_row(::mforms::TreeView *self, int row)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->delete_row(row);
}

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

int TreeViewImpl::add_row(::mforms::TreeView *self)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->add_row();
  return -1;
}

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

int TreeViewImpl::get_selected(::mforms::TreeView *self)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->get_selected();
  return -1;
}

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

void TreeViewImpl::set_selected(::mforms::TreeView *self, const int idx)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->set_selected(idx);
}

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

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

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

int TreeViewImpl::count(::mforms::TreeView *self)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->count();
  return 0;
}

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

void TreeViewImpl::set_row_tag(::mforms::TreeView *self, int row, const string &value)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->set_row_tag(row, value);
}

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

void TreeViewImpl::set_string(::mforms::TreeView *self, int row, int column, const string &value)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->set_string(row, column, value);
}

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

void TreeViewImpl::set_int(::mforms::TreeView *self, int row, int column, int value)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->set_int(row, column, value);
}

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

void TreeViewImpl::set_long(::mforms::TreeView *self, int row, int column, boost::int64_t value)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->set_long(row, column, value);
}

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

void TreeViewImpl::set_check(::mforms::TreeView *self, int row, int column, bool value)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    treeview->set_check(row, column, value);
}

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

string TreeViewImpl::get_row_tag(::mforms::TreeView *self, int row)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->get_row_tag(row);
  return "";
}

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

string TreeViewImpl::get_string(::mforms::TreeView *self, int row, int column)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->get_string(row, column);
  return "";
}

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

int TreeViewImpl::get_int(::mforms::TreeView *self, int row, int column)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->get_int(row, column);
  return 0;
}

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

boost::int64_t TreeViewImpl::get_long(::mforms::TreeView *self, int row, int column)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->get_int(row, column);
  return 0;
}

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

bool TreeViewImpl::get_check(::mforms::TreeView *self, int row, int column)
{
  TreeViewImpl^ treeview= (TreeViewImpl^)ObjectImpl::FromUnmanaged(self);
  if (treeview != nullptr)
    return treeview->get_check(row, column);
  return false;
}

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

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

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

TreeViewImpl::TreeViewImpl(::mforms::TreeView *self)
  : ViewImpl(self)
{
  _model = gcnew SortableTreeModel();
  _current_sort_column = -1;
  _can_sort_column = false;
  _current_sort_order = SortOrder::None;
}

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

int TreeViewImpl::add_column(::mforms::TreeColumnType type, const string &name, int initial_width, bool editable)
{
  TreeViewAdv^ treeview= get_control<TreeViewAdv>();
  
  BindableControl^ nodeControl;
  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:
    {
      NodeIcon^ box= gcnew NodeIcon();
      nodeControl = box;
      break;
    }
  default: // ::mforms::StringColumnType
    {
      NodeTextBox^ box= gcnew NodeTextBox();
      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);

  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 TreeViewImpl::TreeValueNeeded(System::Object^ sender, NodeControlValueEventArgs^ e)
{
  BindableControl^ control= (BindableControl^) sender;
  TreeNodeAdv^ treeNode= e->Node;
  TreeNode^ ourNode= (TreeNode^) treeNode->Tag; // Implicit knowledge, what a design...
  
  // 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)
      {
        std::string path = mforms::App::get()->get_resource_path(NativeToCppString(value));
        e->Value = gcnew Bitmap(CppStringToNative(path));
      }
      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 TreeViewImpl::TreeValuePushed(System::Object^ sender, NodeControlValueEventArgs^ e)
{
  BindableControl^ control= (BindableControl^) sender;
  TreeNodeAdv^ treeNode= e->Node;
  TreeNode^ ourNode= (TreeNode^) treeNode->Tag;

  TreeViewAdv^ tree= ourNode->GetTree();
  ::mforms::TreeView* native_tree= ViewImpl::get_backend_control<::mforms::TreeView>(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(treeNode->Index, control->ParentColumn->Index, new_value))
  {
    // Backend says ok, so update the model.
    if (ourNode != nullptr)
      ourNode->Text[control->ParentColumn->Index] = e->Value->ToString();
  }
}

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

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

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

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

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

  if (tree->Tag != nullptr)
  {
    ::mforms::TreeView* t= ViewImpl::get_backend_control<::mforms::TreeView>(tree);
    if (t != 0)
    {
      int row= args->Node->Index;
      int column= args->Control->ParentColumn->Index;
      t->row_activated(row, column);
    }
  }
}

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

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

  if (tree->Tag != nullptr && e->Button == MouseButtons::Right)
  {
    ::mforms::TreeView* t= ViewImpl::get_backend_control<::mforms::TreeView>(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 TreeViewImpl::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);
  }
}

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

void TreeViewImpl::end_columns()
{
}

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

void TreeViewImpl::clear_rows()
{
  _model->Nodes->Clear();
}

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

void TreeViewImpl::delete_row(int row)
{
  _model->Nodes->RemoveAt(row);
}

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

int TreeViewImpl::add_row()
{
  Node^ node= gcnew TreeNode(get_control<TreeViewAdv>());
  _model->Nodes->Add(node);
  return _model->Nodes->Count - 1;
}

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

int TreeViewImpl::get_selected()
{
  TreeNodeAdv^ node= get_control<TreeViewAdv>()->CurrentNode;
  if (node == nullptr)
    return -1;
  return node->Index;
}

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

void TreeViewImpl::set_selected(const int idx)
{
  TreeViewAdv^ tree= get_control<TreeViewAdv>();
  TreeNodeAdv^ node= tree->Root;

  // Currently the tree is managed as a simple list with columns. Hence setting the selected index
  // means to select the indexed child node of the root node.
  if (tree->Root->Children->Count > idx)
    tree->SelectedNode= tree->Root->Children[idx];
}

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

void TreeViewImpl::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 TreeViewImpl::count()
{
  return _model->Nodes->Count;
}

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

void TreeViewImpl::set_row_tag(int row, const string &value)
{
  MySQL::Forms::TreeNode^ node= (MySQL::Forms::TreeNode^) _model->Nodes[row];
  node->tag= CppStringToNative(value);
}

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

void TreeViewImpl::set_string(int row, int column, const string &value)
{
  MySQL::Forms::TreeNode^ node= (MySQL::Forms::TreeNode^) _model->Nodes[row];
  node->Text[column] = CppStringToNative(value);

  get_control<TreeViewAdv>()->Invalidate(); // Might lead to flickering, investigate if necessary.
}

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

void TreeViewImpl::set_int(int row, int column, int value)
{
  MySQL::Forms::TreeNode^ node= (MySQL::Forms::TreeNode^) _model->Nodes[row];
  node->Text[column] = Convert::ToString(value);

  get_control<TreeViewAdv>()->Invalidate();
}

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

void TreeViewImpl::set_long(int row, int column, boost::int64_t value)
{
  MySQL::Forms::TreeNode^ node= (MySQL::Forms::TreeNode^) _model->Nodes[row];
  node->Text[column] = Convert::ToString(value);

  get_control<TreeViewAdv>()->Invalidate();
}
//--------------------------------------------------------------------------------------------------

void TreeViewImpl::set_check(int row, int column, bool value)
{
  MySQL::Forms::TreeNode^ node= (MySQL::Forms::TreeNode^) _model->Nodes[row];
  node->Text[column] = value ? "1" : "0";

  get_control<TreeViewAdv>()->Invalidate();
}

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

string TreeViewImpl::get_row_tag(int row)
{
  string ret;
  if (_model->Nodes->Count > row && row >= 0)
  {
    MySQL::Forms::TreeNode^ node= (MySQL::Forms::TreeNode^) _model->Nodes[row];
  ret = NativeToCppString(node->tag);
  }
  return ret;
}

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

string TreeViewImpl::get_string(int row, int column)
{
  string ret;
  if (_model->Nodes->Count > row && row >= 0)
  {
    MySQL::Forms::TreeNode^ node= (MySQL::Forms::TreeNode^) _model->Nodes[row];
    ret = NativeToCppString(node->Text[column]);
  }
  return ret;
}

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

int TreeViewImpl::get_int(int row, int column)
{
  MySQL::Forms::TreeNode^ node= (MySQL::Forms::TreeNode^) _model->Nodes[row];
  return Convert::ToInt32(node->Text[column]);
}

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

boost::int64_t TreeViewImpl::get_long(int row, int column)
{
  MySQL::Forms::TreeNode^ node= (MySQL::Forms::TreeNode^) _model->Nodes[row];
  return Convert::ToInt64(node->Text[column]);
}

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

bool TreeViewImpl::get_check(int row, int column)
{
  MySQL::Forms::TreeNode^ node= (MySQL::Forms::TreeNode^) _model->Nodes[row];
  return (node->Text[column] == "Checked") || (node->Text[column] == "1") ? true : false;
}

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

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

    if (_can_sort_column && _model->Comparer!=nullptr)
      _model->Sort();
    else
      tree->Refresh();
  }
}

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