/* 
 * 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
 */

#include "stdafx.h"

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

#include "ConvUtils.h"

using namespace System::IO;

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

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

TreeNodeImpl::TreeNodeImpl(TreeViewAdv ^tree, TreeViewNode ^node)
  : _tree(tree), _node(node), _refcount(0)
{
  _is_root = false;
}

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

TreeNodeImpl::TreeNodeImpl(TreeViewAdv ^tree)
  : _tree(tree), _refcount(0)
{
  _is_root = true;
}

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

void TreeNodeImpl::release()
{
  _refcount--;
  if (_refcount == 0)
    delete this;
}

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

void TreeNodeImpl::retain()
{
  _refcount++;
}

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

TreeNodeViewImpl^ TreeNodeImpl::get_tree_wrapper()
{
  mforms::TreeNodeView *native_tree= ObjectImpl::get_backend_control<mforms::TreeNodeView>(_tree);
  return (TreeNodeViewImpl^)ObjectImpl::FromUnmanaged(native_tree);
}
  
//--------------------------------------------------------------------------------------------------

bool TreeNodeImpl::is_root() const
{
  return _is_root;
}

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

static TreeNodeAdv ^find_node_adv_for(Node ^node, TreeNodeAdv ^root)
{
  if (node == nullptr)
    return nullptr;

  if (node->Parent == nullptr)
    return root;
  TreeNodeAdv ^parent = find_node_adv_for(node->Parent, root);
  if (parent != nullptr)
  {
    int i = node->Index;
    if (i >= 0 && i < parent->Children->Count)
      return parent->Children[i];
  }
  return nullptr;
}

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

TreeNodeAdv^ TreeNodeImpl::find_node_adv()
{
  // apparently no mapping between Node to TreeNodeAdv, so we need
  // to scan it around for it...

  return find_node_adv_for(_node, _tree->Root);
}

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

int TreeNodeImpl::node_index()
{
  if (_is_root)
    return -1;

  return _node->Index;
}

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

Aga::Controls::Tree::TreeModel ^TreeNodeImpl::model() const
{
  return dynamic_cast<Aga::Controls::Tree::TreeModel^>(_tree->Model);
}

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

bool TreeNodeImpl::equals(const mforms::TreeNode &other)
{
  const TreeNodeImpl *oth = dynamic_cast<const TreeNodeImpl*>(&other);
  if (oth)
    return (TreeViewNode ^)oth->_node == (TreeViewNode ^)_node;
  return false;
}

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

bool TreeNodeImpl::is_valid() const
{
  return !_is_root;
}

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

void TreeNodeImpl::set_icon_path(int column, const std::string &icon)
{
  if (!_is_root && !icon.empty())
  {
    bool invalidate = true;
    String ^str_icon = CppStringToNative(icon);
    if (!TreeViewNode::_icon_storage.ContainsKey(str_icon))
    {
      String^ path = CppStringToNative(mforms::App::get()->get_resource_path(icon));
      if (File::Exists(path))
        TreeViewNode::_icon_storage[str_icon] = gcnew Bitmap(path);
      else
        invalidate = false;
    }

    if (invalidate)
    {
      _node->Icon[column] = TreeViewNode::_icon_storage[str_icon];
      _tree->Invalidate();
    }
  }
}

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

void TreeNodeImpl::set_attributes(int column, const mforms::TreeNodeTextAttributes& attrs)
{
  if (!_is_root)
  {
    _node->Attributes[column] = attrs;
    _tree->Invalidate();
  }
}

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

void TreeNodeImpl::set_string(int column, const std::string &value)
{
  if (!_is_root)
  {
    _node->Caption[column] = CppStringToNative(value);

    TreeNodeViewImpl ^wrapper = get_tree_wrapper();
    if (!wrapper->is_frozen() && is<SortableTreeModel^>(model()) && column == wrapper->current_sort_column())
      dynamic_cast<SortableTreeModel^>(model())->Resort();
    _tree->Invalidate();
  }
}

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

void TreeNodeImpl::set_int(int column, int value)
{
  if (!_is_root)
  {
    _node->Caption[column] = Convert::ToString(value);

    TreeNodeViewImpl ^wrapper = get_tree_wrapper();
    if (!wrapper->is_frozen() && is<SortableTreeModel^>(model()) && column == wrapper->current_sort_column())
      dynamic_cast<SortableTreeModel^>(model())->Resort();
    _tree->Invalidate();
  }
}

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

void TreeNodeImpl::set_long(int column, boost::int64_t value)
{
  if (!_is_root)
  {
    _node->Caption[column] = Convert::ToString(value);

    TreeNodeViewImpl ^wrapper = get_tree_wrapper();
    if (!wrapper->is_frozen() && is<SortableTreeModel^>(model()) && column == wrapper->current_sort_column())
      dynamic_cast<SortableTreeModel^>(model())->Resort();
    _tree->Invalidate();
  }
}

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

void TreeNodeImpl::set_bool(int column, bool value)
{
  if (!_is_root)
  {
    _node->Caption[column] = value ? "1" : "0";

    TreeNodeViewImpl ^wrapper = get_tree_wrapper();
    if (!wrapper->is_frozen() && is<SortableTreeModel^>(model()) && column == wrapper->current_sort_column())
      dynamic_cast<SortableTreeModel^>(model())->Resort();
    _tree->Invalidate();
  }
}

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

std::string TreeNodeImpl::get_string(int column) const
{
  if (!_is_root)
    return NativeToCppString(_node->Caption[column]);

  return "";
}

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

int TreeNodeImpl::get_int(int column) const
{
  if (!_is_root)
    return Convert::ToInt32(_node->Caption[column]);

  return 0;
}

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

bool TreeNodeImpl::get_bool(int column) const
{
  if (!_is_root)
    return (_node->Caption[column] == "Checked") || (_node->Caption[column] == "1") ? true : false;

  return false;
}

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

boost::int64_t TreeNodeImpl::get_long(int column) const
{
  if (!_is_root)
    return Convert::ToInt64(_node->Caption[column]);

  return 0;
}

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

int TreeNodeImpl::count() const
{
  if (_is_root)
    return model()->Root->Nodes->Count;
  else
    return _node->Nodes->Count;
}

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

Bitmap^ TreeNodeImpl::get_cached_icon(const std::string& icon_id)
{
  Bitmap ^icon;

  String^ str_icon = CppStringToNative(icon_id);

  if (!TreeViewNode::_icon_storage.ContainsKey(str_icon))
  {
    String^ path = CppStringToNative(mforms::App::get()->get_resource_path(icon_id));
    if (File::Exists(path))
      TreeViewNode::_icon_storage[str_icon] = gcnew Bitmap(path);
  }

  if (TreeViewNode::_icon_storage.ContainsKey(str_icon))
    icon = TreeViewNode::_icon_storage[str_icon];

  return icon;
}

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

std::vector<mforms::TreeNodeRef> TreeNodeImpl::add_node_collection(const mforms::TreeNodeCollectionSkeleton &nodes, int position)
{
  std::vector<mforms::TreeNodeRef> result;

  Node ^parent = _is_root ? model()->Root : _node;

  if (!nodes.captions.empty())
  {
    // The icon will be common to all the nodes in the collection
    Bitmap^ icon;
    if (!nodes.icon.empty())
      icon = get_cached_icon(nodes.icon);

    // Creates an array with all the nodes in the collection
    array<MySQL::Forms::TreeViewNode^>^ added_nodes= gcnew array<MySQL::Forms::TreeViewNode^>(nodes.captions.size());
    for(int index=0; index < added_nodes->Length; index++)
      added_nodes[index] = gcnew MySQL::Forms::TreeViewNode();

    // Now initializes the nodes in the collection with the proper information
    for ( int index = 0; index < added_nodes->Length; index++)
    {
      added_nodes[index]->Caption[0] = CppStringToNative(nodes.captions[index]);
      added_nodes[index]->Tag = _tree; // used by ValuePushed handler
      added_nodes[index]->Icon[0] = icon;
    }

    // Finally we add the nodes to the tree
    for ( int index = 0; index < added_nodes->Length; index++)
    {
      if (position == -1)
        parent->Nodes->Add(added_nodes[index]);
      else
        parent->Nodes->Insert(position++, added_nodes[index]);
      
      result.push_back(mforms::TreeNodeRef(new TreeNodeImpl(_tree, added_nodes[index])));
    }

    // Now adds the child nodes
    if (!nodes.children.empty())
      add_children_from_skeletons(added_nodes, nodes.children);

    // Refreshes the Tree Control
    _tree->Invalidate();
  }

  return result;
}

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

void TreeNodeImpl::add_children_from_skeletons(array<MySQL::Forms::TreeViewNode^>^ parents, const std::vector<mforms::TreeNodeSkeleton>& children)
{
  for (size_t child_index = 0; child_index < children.size(); child_index++)
  {
    // Creates "this" child for each parent
    array<MySQL::Forms::TreeViewNode^>^ added_nodes = gcnew array<MySQL::Forms::TreeViewNode^>(parents->Length);
    for(int index=0; index < added_nodes->Length; index++)
      added_nodes[index] = gcnew MySQL::Forms::TreeViewNode();

    // Gets the attributes for "this" child
    String^ caption = CppStringToNative(children[child_index].caption);
    Bitmap^ icon = get_cached_icon(children[child_index].icon);

    // Initializes the child for the different parents
    for(int added_node_index = 0; added_node_index < parents->Length; added_node_index++)
    {
      added_nodes[added_node_index]->Icon[0] = icon;
      added_nodes[added_node_index]->Caption[0] = caption;
      added_nodes[added_node_index]->Tag = _tree;
      added_nodes[added_node_index]->MyTag = children[child_index].tag;
    }

    // Now inserts each children to it's corresponding parent
    for(int parent_index = 0; parent_index < parents->Length; parent_index++)
      parents[parent_index]->Nodes->Add(added_nodes[parent_index]);

    // If the new nodes will have children as well insert them.
    if (children[child_index].children.size())
      add_children_from_skeletons(added_nodes, children[child_index].children);

  }
}  

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

mforms::TreeNodeRef TreeNodeImpl::insert_child(int index)
{
  Node ^parent = _is_root ? model()->Root : _node;

  MySQL::Forms::TreeViewNode^ child = gcnew MySQL::Forms::TreeViewNode();
  child->Tag = _tree; // Used by ValuePushed handler.
  if (index < 0)
    parent->Nodes->Add(child);
  else
    parent->Nodes->Insert(index, child);

  return mforms::TreeNodeRef(new TreeNodeImpl(_tree, child));
}

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

void TreeNodeImpl::remove_from_parent()
{
  if (!_is_root && _node->Parent != nullptr)
  {
    TreeNodeViewImpl ^wrapper = get_tree_wrapper();
    if (!_node->MyTag.empty())
      wrapper->process_mapping(nullptr, _node->MyTag);

    _node->Parent->Nodes->Remove(_node);
  }
}

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

void TreeNodeImpl::remove_children()
{
  Node ^parent = _is_root ? model()->Root : _node;
  TreeNodeViewImpl ^wrapper = get_tree_wrapper();

  for each (Node ^child in parent->Nodes)
  {
    TreeViewNode ^node = dynamic_cast<TreeViewNode^>(child);
    if (node != nullptr && !node->MyTag.empty())
      wrapper->process_mapping(nullptr, node->MyTag);
  }
  parent->Nodes->Clear();
}

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

mforms::TreeNodeRef TreeNodeImpl::get_child(int index) const
{
  Node ^parent = _is_root ? model()->Root : _node;

  TreeViewNode ^child = dynamic_cast<TreeViewNode^>(parent->Nodes[index]);
  if (child != nullptr)
    return mforms::TreeNodeRef(new TreeNodeImpl(_tree, child));

  return mforms::TreeNodeRef();
}

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

mforms::TreeNodeRef TreeNodeImpl::get_parent() const
{
  if (!_is_root)
  {
    TreeViewNode ^parent = dynamic_cast<TreeViewNode^>(_node->Parent);
    if (parent != nullptr)
      return mforms::TreeNodeRef(new TreeNodeImpl(_tree, parent));
    else
      return mforms::TreeNodeRef(new TreeNodeImpl(_tree));
  }

  return mforms::TreeNodeRef();
}

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

void TreeNodeImpl::expand()
{
  if (!is_root())
    get_parent()->expand();
  TreeNodeAdv ^nodeadv = find_node_adv();
  if (nodeadv != nullptr)
    nodeadv->Expand();
}

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

void TreeNodeImpl::collapse()
{
  TreeNodeAdv ^nodeadv = find_node_adv();
  if (nodeadv != nullptr)
    nodeadv->Collapse();
}

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

bool TreeNodeImpl::is_expanded()
{
  TreeNodeAdv ^nodeadv = find_node_adv();
  if (nodeadv != nullptr)
    return nodeadv->IsExpanded;
  return false;
}

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

void TreeNodeImpl::set_tag(const std::string &tag)
{
  if (!_is_root)
  {
    TreeNodeViewImpl ^wrapper = get_tree_wrapper();
    if (!_node->MyTag.empty())
      wrapper->process_mapping(nullptr, _node->MyTag);
    _node->MyTag = tag;
    wrapper->process_mapping(_node, _node->MyTag);
  }
}

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

std::string TreeNodeImpl::get_tag() const
{
  if (!_is_root)
    return _node->MyTag;

  return "";
}

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

void TreeNodeImpl::set_data(mforms::TreeNodeData *data)
{
  if (!_is_root)
    _node->Data = data;
}

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

mforms::TreeNodeData *TreeNodeImpl::get_data() const
{
  if (!_is_root)
    return _node->Data;

  return NULL;
}

//----------------- TreeViewNode -------------------------------------------------------------------

TreeViewNode::TreeViewNode()
  : _mytag(NULL), _data(NULL)
{
  _attributes = new std::vector<mforms::TreeNodeTextAttributes>();
}

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

TreeViewNode::~TreeViewNode()
{
  delete _attributes;
  delete _mytag;
  if (_data)
    _data->release();
}

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

void TreeViewNode::destroy_data_recursive()
{
  for (int i = 0; i < Nodes->Count; ++i)
    dynamic_cast<MySQL::Forms::TreeViewNode^> (Nodes[i])->destroy_data_recursive();
  Data = nullptr;
};

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

std::string TreeViewNode::MyTag::get()
{
  if (_mytag != NULL)
    return *_mytag;
  return "";
}

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

void TreeViewNode::MyTag::set(std::string s)
{
  if (_mytag)
    delete _mytag;
  _mytag = new std::string(s);
}

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

mforms::TreeNodeData* TreeViewNode::Data::get()
{
  return _data;
}

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

void TreeViewNode::Data::set(mforms::TreeNodeData *d)
{
  if (_data != d)
  {
    if (_data)
      _data->release();
    _data = d;
    if (_data)
      _data->retain();
  }
}

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

String^ TreeViewNode::Caption::get(int index)
{
  if (index < 0 || index >= _captions.Count)
    return "";
  return _captions[index] == nullptr ? "" : _captions[index];
}

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

void TreeViewNode::Caption::set(int index, String^ newText)
{
  while (index >= _captions.Count)
    _captions.Add(nullptr);
  _captions[index] = newText;

  // The simple Text property of the base class is used to identify nodes (e.g. on sort) by the tree.
  // So we have to set text there too to make this work.
  newText = "";
  for each (String ^caption in _captions)
    newText += caption + ":";
  Text = newText;
}

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

Bitmap^ TreeViewNode::Icon::get(int index)
{
  if (_icon.ContainsKey(index))
    return _icon[index];
  return nullptr;
}

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

void TreeViewNode::Icon::set(int index, Bitmap^ newIcon)
{
  _icon[index] = newIcon;
}

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

mforms::TreeNodeTextAttributes TreeViewNode::Attributes::get(int index)
{
  if (index < 0 || index >= (int)_attributes->size())
    return mforms::TreeNodeTextAttributes();
  return (*_attributes)[index];
}

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

void TreeViewNode::Attributes::set(int index, mforms::TreeNodeTextAttributes attributes)
{
  while (index >= (int)_attributes->size())
    _attributes->push_back(mforms::TreeNodeTextAttributes());
    
  (*_attributes)[index] = attributes;
}

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

