// This may look like C code, but it's really -*- C++ -*-
/*
 * Copyright (C) 2008 Emweb bvba, Kessel-Lo, Belgium.
 *
 * See the LICENSE file for terms of use.
 */
#ifndef WT_TREEVIEW_H_
#define WT_TREEVIEW_H_

#include <set>
#include <vector>
#include <boost/unordered_map.hpp>

#include <Wt/WAbstractItemView>
#include <Wt/WJavaScript>
#include <Wt/WModelIndex>
#include <Wt/WSignalMapper>

namespace Wt {

  class WCheckBox;
  class WCssRule;
  class WItemSelectionModel;
  class WText;
  class WTreeViewNode;
  class ToggleButtonConfig;

/*! \class WTreeView Wt/WTreeView Wt/WTreeView
 *  \brief A view class that displays a model as a tree or tree table.
 *
 * The view displays data from a WAbstractItemModel in a tree or tree
 * table. It provides incremental rendering, allowing the display of
 * data models of any size efficiently, without excessive use of
 * client- or serverside resources.
 *
 * The rendering (and editing) of items is handled by a
 * WAbstractItemDelegate, by default it uses WItemDelegate which
 * renders data of all predefined roles (see also Wt::ItemDataRole),
 * including text, icons, checkboxes, and tooltips.
 *
 * The view may support editing of items, if the model indicates
 * support (see the Wt::ItemIsEditable flag). You can define triggers
 * that initiate editing of an item using setEditTriggers(). The
 * actual editing is provided by the item delegate (you can set an
 * appropriate delegate for one column using
 * setItemDelegateForColumn()). Using setEditOptions() you can
 * customize if and how the view deals with multiple editors.
 *
 * By default, all but the first columns are given a width of 150px,
 * and the first column takes the remaining size. <b>Note that this
 * may have as consequence that the first column's size is reduced to
 * 0.</b> Column widths of all columns, including the first column,
 * can be set through the API method setColumnWidth(), and also by the
 * user using handles provided in the header.
 *
 * Optionally, the treeview may be configured so that the first column
 * is always visible while scrolling through the other columns, which
 * may be convenient if you wish to display a model with many
 * columns. Use setColumn1Fixed() to enable this behaviour.
 *
 * If the model supports sorting (WAbstractItemModel::sort()), such as
 * the WStandardItemModel, then you can enable sorting buttons in the
 * header, using setSortingEnabled().
 *
 * You can allow selection on row or item level (using
 * setSelectionBehavior()), and selection of single or multiple items
 * (using setSelectionMode()), and listen for changes in the selection
 * using the selectionChanged() signal.
 *
 * You may enable drag & drop support for this view, whith awareness
 * of the items in the model. When enabling dragging (see
 * setDragEnabled()), the current selection may be dragged, but only
 * when all items in the selection indicate support for dragging
 * (controlled by the \link Wt::ItemIsDragEnabled
 * ItemIsDragEnabled\endlink flag), and if the model indicates a
 * mime-type (controlled by WAbstractItemModel::mimeType()). Likewise,
 * by enabling support for dropping (see setDropsEnabled()), the
 * treeview may receive a drop event on a particular item, at least if
 * the item indicates support for drops (controlled by the \link
 * Wt::ItemIsDropEnabled ItemIsDropEnabled\endlink flag).
 *
 * You may also react to mouse click events on any item, by connecting
 * to one of the clicked() or doubleClicked() signals.
 *
 * \if cpp
 * Usage example:
 * \code
 * // WTreeView will display the data of a model
 * Wt::WAbstractItemModel *model = ...
 *
 * // Create the WTreeView
 * Wt::WTreeView *gitView = new Wt::WTreeView();
 * gitView->resize(300, Wt::WLength::Auto);
 * gitView->setSortingEnabled(false);
 * gitView->setModel(model);
 * gitView->setSelectionMode(SingleSelection);
 * \endcode
 * \endif
 *
 * <h3>Graceful degradation</h3>
 *
 * The view provides a virtual scrolling behavior which relies on Ajax
 * availability. When Ajax is not available, a page navigation bar is
 * used instead, see createPageNavigationBar(). In that case, the widget
 * needs to be given an explicit height using resize() which determines the
 * number of rows that are displayed at a time.
 *
 * A snapshot of the WTreeView: 
 * \image html WTreeView-default-1.png "WTreeView example (default)"
 * \image html WTreeView-polished-1.png "WTreeView example (polished)"
 *
 * \ingroup modelview
 */
class WT_API WTreeView : public WAbstractItemView
{
public:
  /*! \brief Creates a new tree view.
   */
  WTreeView(WContainerWidget *parent = 0);

  ~WTreeView();

#ifndef WT_DEPRECATED_3_0_0
  /*! \brief Sets the column format string (<b>deprecated</b>).
   *
   * \if cpp
   * The DisplayRole data for that column is converted to a string using
   * asString(), with the given format.
   * \elseif java
   * The DisplayRole data for that column is converted to a string using
   * {javadoclink StringUtils#asString(Object)}, with the given format.
   * \endif
   *
   * The default value is "".
   *
   * \deprecated use itemDelegateForColumn() to customize the formatting. This
   *             method will create a new WItemDelegate for the
   *             column, and configure its format.
   */
  void setColumnFormat(int column, const WT_USTRING& format);

  /*! \brief Returns the column format string (<b>deprecated</b>).
   *
   * \sa setColumnFormat()
   *
   * \deprecated use itemDelegateForColumn() to customize the formatting. This
   *             method will try to cast the itemDelegateForColumn() to
   *             a WItemDelegate and return the format.
   */
  WT_USTRING columnFormat(int column) const;
#endif // WT_DEPRECATED_3_0_0

  /*! \brief Expands or collapses a node.
   *
   * \sa expand(), collapse()
   */
  void setExpanded(const WModelIndex&, bool expanded);

  /*! \brief Returns whether a node is expanded.
   *
   * \sa setExpanded()
   */
  bool isExpanded(const WModelIndex& index) const;

  /*! \brief Collapses a node.
   *
   * \sa setExpanded(), expand()
   */
  void collapse(const WModelIndex& index);

  /*! \brief Expands a node.
   *
   * \sa setExpanded(), collapse()
   */
  void expand(const WModelIndex& index);

  /*! \brief Expands all nodes to a depth.
   *
   * Expands all nodes to the given \p depth. A depth of 1 corresponds
   * to the top level nodes.
   *
   * \sa expand()
   */
  void expandToDepth(int depth);

  /*! \brief Sets whether toplevel items are decorated.
   *
   * By default, top level nodes have expand/collapse and other lines
   * to display their linkage and offspring, like any node.
   *
   * By setting \p show to \c false, you can hide these decorations
   * for root nodes, and in this way mimic a plain list. You could also
   * consider using a WTableView instead.
   */
  void setRootIsDecorated(bool show);

  /*! \brief Returns whether toplevel items are decorated.
   *
   * \sa setRootIsDecorated()
   */
  bool rootIsDecorated() const { return rootIsDecorated_; }

  virtual void resize(const WLength& width, const WLength& height);

  /*! \brief %Signal emitted when a node is collapsed.
   *
   * \sa setExpanded(), expanded()
   */
  Signal<WModelIndex>& collapsed() { return collapsed_; }

  /*! \brief %Signal emitted when a node is expanded.
   *
   * \sa setExpanded(), collapsed()
   */
  Signal<WModelIndex>& expanded() { return expanded_; }

  virtual WWidget *itemWidget(const WModelIndex& index) const;
  virtual void setModel(WAbstractItemModel *model);

  /*! \brief Sets the column width.
   *
   * For a model with \link WAbstractItemModel::columnCount()
   * columnCount()\endlink == \p N, the initial width of columns 1..\p
   * N is set to 150 pixels, and column 0 will take all remaining
   * space.
   *
   * \note The actual space occupied by each column is the column width
   *       augmented by 7 pixels for internal padding and a border.
   *
   * \sa setRowHeight()
   */
  virtual void setColumnWidth(int column, const WLength& width);
  virtual void setAlternatingRowColors(bool enable);
  virtual void setRowHeight(const WLength& rowHeight);
  virtual void setHeaderHeight(const WLength& height);
#ifndef WT_CNOR
  using WAbstractItemView::setHeaderHeight;
#endif
  virtual void setColumnBorder(const WColor& color);
  virtual void setColumnHidden(int column, bool hidden);
  virtual void setRowHeaderCount(int count);

  virtual int pageCount() const;
  virtual int pageSize() const;
  virtual int currentPage() const;
  virtual void setCurrentPage(int page);

  virtual void scrollTo(const WModelIndex& index,
			ScrollHint hint = EnsureVisible);

protected:
  virtual void render(WFlags<RenderFlag> flags);
  virtual void enableAjax();

private:
  typedef boost::unordered_map<WModelIndex, WTreeViewNode *> NodeMap;

  WModelIndexSet       expandedSet_;
  NodeMap              renderedNodes_;
  bool                 renderedNodesAdded_;
  WTreeViewNode       *rootNode_;
  WCssTemplateRule    *rowHeightRule_, *rowWidthRule_, 
                      *rowContentsWidthRule_, *c0WidthRule_;
  WCssRule            *borderColorRule_;

  bool                 rootIsDecorated_;
  bool                 column1Fixed_;

  Signal<WModelIndex>  collapsed_;
  Signal<WModelIndex>  expanded_;

  // in rows, as indicated by the current position of the viewport:
  int viewportTop_;
  int viewportHeight_;

  // the firstRenderedRow may differ from viewportTop_, because the user
  // adjusted the view port slightly, but not enough to trigger a correction
  //
  // the validRowCount may differ from viewportHeight_ as a result of
  // expanding or collapsing nodes, or inserting and deleting rows.
  // it takes into account that an expanded node may be incomplete, and
  // thus everything beyond is irrelevant
  int firstRenderedRow_;
  int validRowCount_;

  // rendered nodes in memory (including those collapsed and not included in
  // actualRenderedRowCount_), but excluding nodes that are simply there since
  // some of its children are rendered
  int nodeLoad_;

  WContainerWidget *headers_, *headerContainer_;
  WContainerWidget *contents_, *contentsContainer_;
  WContainerWidget *scrollBarC_;

  int firstRemovedRow_, removedHeight_;

  JSignal<std::string, std::string, std::string,
          std::string, WMouseEvent> itemEvent_;

  ToggleButtonConfig *expandConfig_;

  JSlot tieRowsScrollJS_;
  
  virtual ColumnInfo createColumnInfo(int column) const;

  void defineJavaScript();
  void rerenderHeader();
  void rerenderTree();
  void setup();

  virtual void scheduleRerender(RenderState what);

  void modelColumnsInserted(const WModelIndex& parent, int start, int end);
  void modelColumnsAboutToBeRemoved(const WModelIndex& parent,
				    int start, int end);
  void modelColumnsRemoved(const WModelIndex& parent, int start, int end);
  void modelRowsInserted(const WModelIndex& parent, int start, int end);
  void modelRowsAboutToBeRemoved(const WModelIndex& parent, int start, int end);
  void modelRowsRemoved(const WModelIndex& parent, int start, int end);
  virtual void modelDataChanged(const WModelIndex& topLeft,
				const WModelIndex& bottomRight);
  virtual void modelLayoutAboutToBeChanged();
  virtual void modelLayoutChanged();

  void onViewportChange(WScrollEvent event);
  void contentsSizeChanged(int width, int height);
  void onItemEvent(std::string nodeId, std::string type,
		   std::string extra1, std::string extra2, WMouseEvent event);
  void setRootNodeStyle();
  void setCollapsed(const WModelIndex& index);

  int calcOptimalFirstRenderedRow() const;
  int calcOptimalRenderedRowCount() const;

  void shiftModelIndexes(const WModelIndex& parent, int start, int count);
  static int shiftModelIndexes(const WModelIndex& parent, int start, int count,
			       WAbstractItemModel *model, WModelIndexSet& set);

  void addRenderedNode(WTreeViewNode *node);
  void removeRenderedNode(WTreeViewNode *node);

  void adjustToViewport(WTreeViewNode *changed = 0);

  int pruneNodes(WTreeViewNode *node, int theNodeRow);
  int adjustRenderedNode(WTreeViewNode *node, int theNodeRow);

  WWidget *widgetForIndex(const WModelIndex& index) const;
  WTreeViewNode *nodeForIndex(const WModelIndex& index) const;

  int subTreeHeight(const WModelIndex& index,
		    int lowerBound = 0,
		    int upperBound = std::numeric_limits<int>::max());
  int renderedRow(const WModelIndex& index,
		  WWidget *w,
		  int lowerBound = 0,
		  int upperBound = std::numeric_limits<int>::max());
  int getIndexRow(const WModelIndex& index,
		  const WModelIndex& ancestor,
		  int lowerBound = 0,
		  int upperBound = std::numeric_limits<int>::max());

  std::string columnStyleClass(int column) const;

  int renderLowerBound() const;
  int renderUpperBound() const;

  void renderedRowsChanged(int row, int count);

  WContainerWidget *headerRow();

  virtual bool internalSelect(const WModelIndex& index, SelectionFlag option);
  virtual void selectRange(const WModelIndex& first, const WModelIndex& last);

  void expandChildrenToDepth(const WModelIndex& index, int depth);

  void updateColumnWidth(int columnId, int width);

  virtual WContainerWidget* headerContainer() { return headerContainer_; }

  virtual WWidget *headerWidget(int column, bool contentsOnly = true) ;

  friend class WTreeViewNode;
  friend class ContentsContainer;
};

}

#endif // WT_TREEVIEW_H_
