// 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 WTREE_VIEW_H_
#define WTREE_VIEW_H_

#include <set>
#include <vector>

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

namespace Wt {

class WAbstractItemModel;
class WCheckBox;
class WCssTemplateRule;
class WImage;
class WTreeViewNode;

/*! \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. Data of all predefined roles is
 * displayed (see also ItemDataRole), including text, icons,
 * checkboxes, and tooltips .
 *
 * By default, all but the first columns are given a width of 150px,
 * and the first column takes the remaining size. This can be changed
 * through the API (setColumnWidth(), and also by the user using
 * handles provided in the header.
 *
 * If the model supports sorting (WAbstractItemModel::sort()), 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 \link WTreeView::selectionChanged
 * selectionChanged\endlink signal.
 *
 * You may also react to mouse click events on any item, by connecting
 * to one of the \link WTreeView::clicked clicked\endlink or \link
 * WTreeView::doubleClicked doubleClicked\endlink signals.
 *
 * \note Column insertions and deletions are currently not updated in the view.
 *       Therefore you should make sure that columns are set in the model
 *       before using setModel() and never changed later.
 *
 * \ingroup modelview
 */
class WT_API WTreeView : public WCompositeWidget
{
public:
  /*! \brief Create a new tree view.
   */
  WTreeView(WContainerWidget *parent);

  /*! \brief Destructor.
   */
  ~WTreeView();

  /*! \brief Set the model.
   *
   * The view will render the data in the given <i>model</i>. Changes
   * to the model are reflected in the view.
   *
   * When resetting a model, all nodes are initially collapsed, the
   * selection is cleared, and the root index corresponds to the
   * model's top level node (see setRootIndex()).
   *
   * The initial model is 0.
   *
   * Ownership of the model is not transferred (and thus the
   * previously set model is not deleted).
   *
   * \sa setRootIndex()
   */
  void setModel(WAbstractItemModel *model);

  /*! \brief Returns the model.
   *
   * \sa setModel()
   */
  WAbstractItemModel *model() const { return model_; }

  /*! \brief Set the root index.
   *
   * The root index is the model index that is considered the root
   * node. This node itself is not rendered, but all its children are
   * the top level nodes.
   *
   * The default value is WModelIndex(), corresponding to the
   * invisible root.
   *
   * \sa setModel()
   */
  void setRootIndex(const WModelIndex& rootIndex);

  /*! \brief Returns the root index.
   *
   * \sa setRootIndex()
   */
  const WModelIndex& rootIndex() const { return rootIndex_; }

  /*! \brief Set the row height.
   *
   * The view assumes that all rows are of the same height. Use this
   * method to set the height.
   *
   * The default value is 20 pixels.
   */
  void setHeaderHeight(const WLength& height);

  /*! \brief Return the header height.
   *
   * \sa setHeaderHeight()
   */
  const WLength& headerHeight() const { return headerHeight_; }

  /*! \brief Set the row height.
   *
   * The view assumes that all rows are of the same height. Use this
   * method to set the height.
   *
   * The default value is 20 pixels.
   *
   * \note The height must be specified in WLength::Pixel units.
   *
   * \sa setColumnWidth()
   */
  void setRowHeight(const WLength& rowHeight);

  /*! \brief Return the row height.
   */
  const WLength& rowHeight() const { return rowHeight_; }

  /*! \brief Set the column width.
   *
   * You can specify the column width for all columns, except for the
   * first column.
   *
   * For a model with \link WAbstractItemModel::columnCount()
   * columnCount()\endlink == <i>N</i>, the initial width of columns
   * 1..<i>N</i> is set to 150 pixels, and column 0 will take all
   * remaining space.
   *
   * \sa setRowHeight()
   */
  void setColumnWidth(int column, const WLength& width);
 
  /*! \brief Returns the column width.
   *
   * \sa setColumnWidth()
   */
  WLength columnWidth(int column) const;

  /*! \brief Set the content alignment for a column.
   *
   * The default value is \link Wt::AlignLeft AlignLeft\endlink.
   */
  void setColumnAlignment(int column, HorizontalAlignment alignment);

  /*! \brief Returns the content alignment for a column.
   *
   * \sa setColumnAlignment()
   */
  HorizontalAlignment columnAlignment(int column) const;

  /*! \brief Sets the base urls for icons.
   *
   * This widget relies on several icons that are distributed together
   * with %Wt for drawing icons, lines, and backgrounds.
   *
   * The default location for the image pack is <i>resourcesURL</i>.
   *
   * The default value for <i>resourcesURL</i> is "resources/". This
   * value may be overridden with a URL that points to a folder where
   * these files are located, by configuring the <i>resourcesURL</i>
   * property in your %Wt configuration file.
   */
  void setImagePack(const std::string& uri);

  /*! \brief Returns the base url for icons.
   *
   * \sa setImagePack()
   */
  std::string imagePack() const { return imagePack_; }

  /*! \brief Expand or collapse 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 Collapse a node.
   *
   * \sa setExpanded(), expand()
   */
  void collapse(const WModelIndex& index);

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

  /*! \brief Set if alternating row colors are to be used.
   *
   * Configure whether rows get an alternating background color. These
   * are implemented by using a background image on the root node, like:
   * \image html stripe-30px.gif "Sample image use for alternating row colors"
   *
   * The image that is used is
   * imagePack() + "/stripes/stripe-<i>n</i>px.gif", where <i>n</i> is the
   * row height. In the resource folder are images pregenerated for
   * one color and row sizes from 10 to 30px.
   *
   * The default value is false.
   *
   * \sa setImagePack()
   */
  void setAlternatingRowColors(bool enable);

  /*! \brief Returns whether alternating row colors are used.
   *
   * \sa setAlternatingRowColors()
   */
  bool alternatingRowColors() const { return alternatingRowColors_; }

  /*! \brief Set 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 <i>show</i> to false, you can hide these decorations
   * for root nodes, and in this way mimic a plain list.
   *
   * \sa Ext::TableView
   */
  void setRootIsDecorated(bool show);

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

  /*! \brief Sort the data according to a column.
   *
   * Sorts the data according to data in column <i>column</i> and sort
   * order <i>order</i>.
   *
   * \sa WAbstractItemModel::sort()
   */
  void sortByColumn(int column, SortOrder order);

  /*! \brief Enable sorting.
   *
   * Enable or disable sorting by the user.
   *
   * Sorting is enabled by default.
   *
   * \sa WAbstractItemModel::sort()
   */
  void setSortingEnabled(bool enabled);

  /*! \brief Returns whether sorting is enabled.
   *
   * \sa setSortingEnabled()
   */
  bool isSortingEnabled() const { return sorting_; }

  /*! \brief Change the selection behaviour.
   *
   * By default, selection operates on rows (\link Wt::SelectRows
   * SelectRows\endlink), in which case model indexes will always be
   * in the first column (column 0).
   *
   * Alternatively, you can allow selection for individual items
   * (\link Wt::SelectItems SelectItems\endlink).
   *
   * \sa setSelectionMode()
   */
  void setSelectionBehavior(SelectionBehavior behavior);

  /*! \brief Returns the selection behaviour.
   *
   * \sa setSelectionBehavior()
   */
  SelectionBehavior selectionBehavior() const { return selectionBehavior_; }

  /*! \brief Set the selection mode.
   *
   * By default selection is disabled (\link Wt::NoSelection
   * NoSelection \endlink).
   *
   * \sa setSelectionBehavior()
   */
  void setSelectionMode(SelectionMode mode);

  /*! \brief Returns the selection mode.
   *
   * \sa setSelectionMode()
   */
  SelectionMode selectionMode() const { return selectionMode_; }

  /*! \brief Sets the selected items.
   *
   * Replaces the current selection with <i>indexes</i>.
   *
   * \sa select()
   */
  void setSelectedIndexes(const WModelIndexSet& indexes);

  /*! \brief Select a single item.
   *
   * \sa setSelectedIndexes()
   */
  void select(const WModelIndex& index, SelectionFlag option = Select);

  /*! \brief Returns wheter an item is selected.
   *
   * \sa selectedIndexes(), select()
   */
  bool isSelected(const WModelIndex& index) const;

  /*! \brief Returns the set of selected items.
   *
   * The model indexes are returned as a set, topologically ordered (in
   * the order they appear in the view).
   *
   * \sa setSelectedIndexes()
   */
  WModelIndexSet selectedIndexes() const { return selection_; }

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

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

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

  /*! \brief %Signal emitted when an item is clicked.
   *
   * \sa doubleClicked
   */
  Signal<WModelIndex> clicked;

  /*! \brief %Signal emitted when an item is double clicked.
   *
   * \sa doubleClicked
   */
  Signal<WModelIndex> doubleClicked;

  /*! \brief %Signal emitted when the selection is changed.
   *
   * \sa select(), setSelectionMode(), setSelectionBehavior()
   */
  Signal<void> selectionChanged;

private:
  struct ColumnInfo {
    WCssTemplateRule   *styleRule;
    int                 id;
    SortOrder           sortOrder;
    HorizontalAlignment alignment;

    std::string styleClass() const;

    ColumnInfo();
    ColumnInfo(WTreeView *view, WApplication *app, int id);
  };

  struct CheckedInfo {
    WModelIndex  index;
    WCheckBox   *checkBox;

    CheckedInfo(const WModelIndex& anIndex, WCheckBox *aCheckBox)
      : index(anIndex), checkBox(aCheckBox) { }

    CheckedInfo() { }
  };

  typedef std::map<WModelIndex, WTreeViewNode *> NodeMap;

  WAbstractItemModel *model_;
  WModelIndex         rootIndex_;
  WLength             rowHeight_, headerHeight_;
  WModelIndexSet      expanded_;
  NodeMap             renderedNodes_;
  WTreeViewNode      *rootNode_;
  std::string         imagePack_;
  WCssTemplateRule   *rowHeightRule_, *headerHeightRule_;
  bool                alternatingRowColors_;
  bool                rootIsDecorated_;
  SelectionBehavior   selectionBehavior_;
  SelectionMode       selectionMode_;
  WModelIndexSet      selection_;
  bool                sorting_;

  std::vector<void *> expandedRaw_, selectionRaw_;

  std::vector<ColumnInfo> columns_;
  int                     nextColumnId_;

  WSignalMapper<CheckedInfo> *checkedChangeMapper_;
  WSignalMapper<int>         *clickedForSortMapper_;

  // 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_;

  int lastContentsHeight_;

  int currentSortColumn_;

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

  int firstRemovedRow_, removedHeight_;

  std::vector<boost::signals::connection> modelConnections_;
  JSlot resizeHandleMDownJS_, resizeHandleMMovedJS_, resizeHandleMUpJS_,
    tieContentsHeaderScrollJS_, itemClickedJS_, itemDoubleClickedJS_;

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

  void rerender();
  void rerenderHeader();
  void rerenderTree();

  void modelColumnsInserted(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);
  void modelDataChanged(const WModelIndex& topLeft,
			const WModelIndex& bottomRight);
  void modelHeaderDataChanged(Orientation orientation, int start, int end);
  void modelLayoutAboutToBeChanged();
  void modelLayoutChanged();

  void onViewportChange(WScrollEvent event);
  void onCheckedChange(CheckedInfo info);
  void toggleSortColumn(int column);
  void onItemEvent(std::string nodeId, int columnId, std::string type,
		   std::string modifierStr);
  void setRootNodeStyle();
  void setCollapsed(const WModelIndex& index);

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

  void shiftModelIndexes(const WModelIndex& parent, int start, int count);
  static void 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);

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

  WWidget *widgetForIndex(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());

  int columnCount() const;
  std::string columnStyleClass(int column) const;

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

  void renderedRowsChanged(int row, int count);
  void convertToRaw(WModelIndexSet& set, std::vector<void *>& result);

  WWidget *headerWidget(int column);
  WText   *headerTextWidget(int column);
  WImage  *headerSortIconWidget(int column);

  void selectionHandleClick(const WModelIndex& index, int modifiers);
  bool internalSelect(const WModelIndex& index, SelectionFlag option);
  void selectRange(const WModelIndex& first, const WModelIndex& last);
  void extendSelection(const WModelIndex& index);
  void clearSelection();

  friend class WTreeViewNode;
};

}

#endif // WTREE_VIEW_H_
