/* 
 * 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
 */
#ifndef _MFORMS_TREENODEVIEW_H_
#define _MFORMS_TREENODEVIEW_H_

#include <mforms/view.h>
#include <boost/cstdint.hpp>

/**
 * Implementation of a control class for a treeview control based on node objects.
 */

namespace mforms {
  class TreeNodeView;
  class ContextMenu;
  
  /** Determines what type a column should have (mainly describing the column *editor*). */
  enum TreeColumnType
  {
    StringColumnType, //!< simple string, with text entry editor
    IntegerColumnType, //!< numeric field, with text entry editor
    LongIntegerColumnType, //!< 64bit numeric field, with text entry editor
    CheckColumnType, //!< boolean field, with checkbox
    IconColumnType, //!< icon field, value is the icon path
    IconStringColumnType = IconColumnType
  };
  
  /** Options used to customize what to show in the tree. */
  enum TreeOptions
  {
    TreeDefault          = 0,
    TreeNoColumns        = 1 << 3, //!< On non-Windows platforms columns are always on, so switch them on
    //! on Windows too by default and use this flag to switch them off, if really needed.
    //! At least gtk has problems with arbitrary items for a tree. Treeview in gtk is
    //! built on View-Source model, where source is a row/column based thing. That may
    //! require some hacking to support NoColums in gtk, so really think if that worth it.
    TreeAllowReorderRows = 1 << 4,
    TreeShowColumnLines  = 1 << 5, //!< show column separator lines
    TreeShowRowLines     = 1 << 6, //!< show row separator lines
    TreeNoBorder         = 1 << 7, //!< Switch off the border around the control. Default is to show the border.
    TreeSidebar          = 1 << 8, //!< sidebar style treeview
    TreeNoHeader         = 1 << 9, //!< disable header from treeview, unsupported on Windows.
    TreeShowHeader       = 0,      //!< for backwards compatibility
    TreeFlatList         = 1 << 10, //!< no child items expected
    TreeAltRowColors     = 1 << 11, //!< enable alternating row colors
    TreeSizeSmall        = 1 << 12, //!< small text
    TreeIndexOnTag       = 1 << 13  //!< keep a node index on the tags (use with node_with_tag)
  };
  
#ifndef SWIG
  inline TreeOptions operator| (TreeOptions a, TreeOptions b)
  {
    return (TreeOptions) ((int) a | (int) b);
  }
#endif

  enum TreeSelectionMode
  {
    TreeSelectSingle, // 0 or 1 item selected
    TreeSelectMultiple // 0+ items selection
  };

  typedef struct TextAttributes TreeNodeTextAttributes;  

  // This struct represents the data conforming a node
  // including a collection of childs
  struct MFORMS_EXPORT TreeNodeSkeleton
  {
  public:
    TreeNodeSkeleton() {};
    TreeNodeSkeleton(const std::string& caption, const std::string& icon, const std::string& tag);
    std::string caption;
    std::string icon;
    std::string tag;
    std::vector<TreeNodeSkeleton> children;
  };

  // This struct represents a collection of nodes sharing tag, icon and structure
  struct MFORMS_EXPORT TreeNodeCollectionSkeleton
  {
  public:
    TreeNodeCollectionSkeleton() {};
    TreeNodeCollectionSkeleton(const std::string& icon);
    std::string icon;
    std::vector<TreeNodeSkeleton> children;
    std::vector<std::string> captions;
  };

  class MFORMS_EXPORT TreeNodeData {
  protected:
    int _refcount;
  public:
    TreeNodeData() : _refcount(0) {}
    virtual ~TreeNodeData() {}
    void retain() { _refcount++; }
    void release() { _refcount--; if (_refcount == 0) delete this; }
  };


  class TreeNode;
  struct MFORMS_EXPORT TreeNodeRef
  {
  private:
    TreeNode *node;

  public:
#ifndef SWIG
    TreeNodeRef(TreeNode *anode);
    TreeNode *ptr() { return node; }
#endif
  public:
    TreeNodeRef() : node(0) {}
    TreeNodeRef(const TreeNodeRef &other);
    ~TreeNodeRef();
    TreeNodeRef &operator= (const TreeNodeRef &other);
    TreeNode *operator->() const;
    TreeNode *operator->();
    operator bool () const { return node !=0; }
    bool operator == (const TreeNodeRef &other) const;
    bool operator != (const TreeNodeRef &other) const;

    bool is_valid();
  };

  class MFORMS_EXPORT TreeNode
  {
    friend struct TreeNodeRef;

  protected:
    virtual void release() = 0;
    virtual void retain() = 0;
  public:
    virtual ~TreeNode() {};
    
    virtual bool equals(const TreeNode &other) = 0;
    virtual bool is_valid() const = 0;
    
    virtual void set_icon_path(int column, const std::string &icon) = 0;

    virtual void set_string(int column, const std::string &value) = 0;
    virtual void set_int(int column, int value) = 0;
    virtual void set_long(int column, boost::int64_t value) = 0;
    virtual void set_bool(int column, bool value) = 0;

    virtual void set_attributes(int column, const TreeNodeTextAttributes &attrs) = 0;

    virtual std::string get_string(int column) const = 0;
    virtual int get_int(int column) const = 0;
    virtual boost::int64_t get_long(int column) const = 0;
    virtual bool get_bool(int column) const = 0;
    
    virtual int count() const = 0;
    virtual TreeNodeRef add_child() { return insert_child(-1); }
    virtual TreeNodeRef insert_child(int index) = 0;
    virtual void remove_from_parent() = 0;
    virtual void remove_children(); // default impl provided, subclasses may override to provide faster impl
    virtual TreeNodeRef get_child(int index) const = 0;
    virtual TreeNodeRef get_parent() const = 0;
    virtual TreeNodeRef find_child_with_tag(const std::string &tag); // this will search the subnodes sequentially
 
    virtual std::vector<mforms::TreeNodeRef> add_node_collection(const TreeNodeCollectionSkeleton &nodes, int position = -1) = 0;

    virtual void expand() = 0;
    virtual void collapse() = 0;
    virtual bool is_expanded() = 0;

    virtual void set_tag(const std::string &tag) = 0;
    virtual std::string get_tag() const = 0;

    virtual void set_data(TreeNodeData *data) = 0;
    virtual TreeNodeData *get_data() const = 0;
  };
  

#ifndef DOXYGEN_SHOULD_SKIP_THIS
#ifndef SWIG
  struct TreeNodeViewImplPtrs
  {
    bool (__stdcall *create)(TreeNodeView *self, TreeOptions options);
    
#if defined(_WIN32)
    int (__stdcall *add_column)(TreeNodeView *self, TreeColumnType type, const std::string &name, int initial_width, bool editable);
#else
    int (__stdcall *add_column)(TreeNodeView *self, TreeColumnType type, const std::string &name, int initial_width, bool editable, bool attributed);
#endif
    void (__stdcall *end_columns)(TreeNodeView *self);
    
    void (__stdcall *clear)(TreeNodeView *self);

    TreeNodeRef (__stdcall *root_node)(TreeNodeView *self);

    void (__stdcall *set_row_height)(TreeNodeView *self, int height);
    
    void (__stdcall *set_allow_sorting)(TreeNodeView *self, bool);
    
    void (__stdcall *freeze_refresh)(TreeNodeView *self, bool);
    
    void (__stdcall *set_selection_mode)(TreeNodeView *self, TreeSelectionMode mode);
    TreeSelectionMode (__stdcall *get_selection_mode)(TreeNodeView *self);
    
    std::list<TreeNodeRef> (__stdcall *get_selection)(TreeNodeView *self);
    TreeNodeRef (__stdcall *get_selected_node)(TreeNodeView *self);

    void (__stdcall *clear_selection)(TreeNodeView *self);
    void (__stdcall *set_selected)(TreeNodeView *self, TreeNodeRef node, bool state);
    
    int (__stdcall *row_for_node)(TreeNodeView *self, TreeNodeRef node);
    TreeNodeRef (__stdcall *node_at_row)(TreeNodeView *self, int row);
    TreeNodeRef (__stdcall *node_with_tag)(TreeNodeView *self, const std::string &tag);

    void (__stdcall *set_column_visible)(TreeNodeView *self, int column, bool flag);
    bool (__stdcall *get_column_visible)(TreeNodeView *self, int column);
  };
#endif
#endif
  
  class ContextMenu;
  
  /** Control to show nodes in multiple columns in the form of a tree. 
   
   Before adding items, you must first define the columns and the content types with
   add_column() and end_columns()
   */
  class MFORMS_EXPORT TreeNodeView : public View
  {
  public:
    TreeNodeView(TreeOptions options);
    ~TreeNodeView();

    /** Adds a column to be displayed in the tree.
     
     @param type - type of value to be displayed in column
     @param name - name/caption to show in header
     @param initia_width - width in pixels of the column
     @param editable - whether editing is allowed for rows in this column
     */
    int add_column(TreeColumnType type, const std::string &name, int initial_width, bool editable = false, bool attributed = false);
    /** Must be called after needed add_column() calls are finished. */
    void end_columns();

    TreeColumnType get_column_type(int column);
    
    /** Removes all nodes */
    void clear();
    TreeNodeRef root_node();

    TreeNodeRef add_node();

    /** Selects selection type */
    void set_selection_mode(TreeSelectionMode mode);
    /** Returns the current selection mode */
    TreeSelectionMode get_selection_mode();

    /** Returns a list of the selected nodes */
    std::list<TreeNodeRef> get_selection();
    /** Returns the selected node. In case multiple selected nodes exist, only the 1st is returned */
    TreeNodeRef get_selected_node();
    int get_selected_row();
    void clear_selection();
    
    /** Sets the selection state of the node */
    void select_node(TreeNodeRef node);
    void set_node_selected(TreeNodeRef node, bool flag);
    
    int row_for_node(TreeNodeRef node);
    TreeNodeRef node_at_row(int row);

    /** Requires TreeIndexOnTag */
    TreeNodeRef node_with_tag(const std::string &tag);

    /** Sets the height of a row in pixels */
    void set_row_height(int height);
    
    /** Toggles sorting of rows when the user clicks on a column header
     
     Be careful with the get_selected() and set_selected() methods when sorting is enabled,
     as the index of a row will change depending on the sorting. Use get_row_tag() for
     an immutable identifier for rows.
     
     \warning When sorting is enabled, you must use freeze_refresh()/thaw_refresh() when making changes to tree,
     otherwise the contents will become corrupted.
     */
    void set_allow_sorting(bool flag);

    /** Freezes refresh of tree display. Use when updating a large number of rows or when sorting is enabled. */
    void freeze_refresh();
    
    /** Unfreezes a previously done freeze_refresh() */
    void thaw_refresh();

    void set_column_visible(int column, bool flag);

    bool get_column_visible(int column);
    
#ifndef SWIG
    /** Signal emitted when the selected row is changed 
     
     In Python use add_changed_callback()
     */
    boost::signals2::signal<void ()>* signal_changed() {return &_signal_changed;}
    /** Signal emitted when the user double-clicks a cell.
     
     Arguments passed are row node and column index.
     
     In Python use add_activated_callback()
     */
    boost::signals2::signal<void (TreeNodeRef, int)>* signal_node_activated() {return &_signal_activated;}

    boost::signals2::signal<void (TreeNodeRef, bool)>* signal_expand_toggle() {return &_signal_expand_toggle;}
    
    /** Sets a callback to handle changed made by user to tree cells.
     
     if this handler is set, it must call set() itself whenever a cell is edited
     otherwise changes will not be committed.
     */
    void set_cell_edit_handler(const boost::function<void (TreeNodeRef, int, std::string)> &handler);
#endif
    /** Sets a context menu to be attached to the treeview, to be shown on right click
     
     Note: Ownership of the context menu remains with the caller and it will not be freed
     when this object is deleted. */
    void set_context_menu(ContextMenu *menu);
    
    /** Returns the context menu object attached to the treeview */
    ContextMenu *get_context_menu() { return _context_menu; }
    
    
  public:
    // backwards compatibility with TreeView
    
    int count() { return root_node()->count(); }
    
#ifndef DOXYGEN_SHOULD_SKIP_THIS   
#ifndef SWIG
  public: 
    // Following methods are for internal use.
    void changed();
    void node_activated(TreeNodeRef row, int column);
    
    // to be called BEFORE a node is expanded or AFTER its collapsed
    void expand_toggle(TreeNodeRef row, bool expanded);

    // To be called when user edits a cell. If this returns true, the cell value should be
    // updated by the caller, otherwise it should be left unchanged.
    bool cell_edited(TreeNodeRef row, int column, const std::string &value);
#endif
#endif
  protected:
    TreeNodeViewImplPtrs *_treeview_impl;
    boost::signals2::signal<void ()>  _signal_changed;
    boost::signals2::signal<void (TreeNodeRef, int)>  _signal_activated;
    boost::signals2::signal<void (TreeNodeRef, bool)> _signal_expand_toggle;
    boost::function<void (TreeNodeRef, int, std::string)> _cell_edited;
    ContextMenu *_context_menu;
    std::vector<TreeColumnType> _column_types;
    int _update_count;
    bool _index_on_tag;
  };
}

#endif /* _TREENODEVIEW_H_ */
