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

#include <Wt/WAbstractProxyModel>

namespace Wt {

/*! \class WBatchEditProxyModel Wt/WBatchEditProxyModel Wt/WBatchEditProxyModel
 *  \brief A proxy model for %Wt's item models that provides batch editing.
 *
 * This proxy model presents data from a source model, and caches any
 * editing operation without affecting the underlying source model,
 * until commitAll() or revertAll() is called. In this way, you can
 * commit all the editing in batch to the underlying source model,
 * only when the user confirms the changes.
 *
 * All editing operations are supported:
 *  - changing data (setData())
 *  - inserting and removing rows (insertRows() and removeRows())
 *  - inserting and removing columns (insertColumns() and removeColumns())
 *
 * The model supports both simple tabular models, as well as
 * hierarchical (tree-like / treetable-like) models, with children
 * under items in the first column.
 *
 * Default values for a newly inserted row can be set using
 * setNewRowData() and flags for its items using setNewRowFlags().
 *
 * \ingroup modelview
 */
class WT_API WBatchEditProxyModel : public WAbstractProxyModel
{
public:
  /*! \brief Constructor.
   */
  WBatchEditProxyModel(WObject *parent = 0);

  /*! \brief Destructor.
   */
  virtual ~WBatchEditProxyModel();

  /*! \brief Returns whether changes have not yet been committed.
   *
   * Returns whether have been made to the proxy model, which could be
   * committed using commitAll() or reverted using revertAll().
   */
  bool isDirty() const;

  /*! \brief Commits changes.
   *
   * This commits all changes to the source model.
   *
   * \sa revertAll()
   */
  void commitAll();

  /*! \brief Reverts changes.
   *
   * This reverts all changes.
   *
   * \sa commitAll()
   */
  void revertAll();

  /*! \brief Sets default data for a newly inserted row.
   *
   * You can use this method to initialize data for a newly inserted
   * row.
   */
  void setNewRowData(int column, const boost::any& data,
		     int role = DisplayRole);

  /*! \brief Sets the item flags for items in a newly inserted row.
   *
   * By default, flags() will return ItemIsSelectable.
   */
  void setNewRowFlags(int column, WFlags<ItemFlag> flags);

  /*! \brief Configures data used to indicate a modified item.
   *
   * This sets \p data for item data role \p role to be returned by
   * data() for an item that is dirty (e.g. because it belongs to a
   * newly inserted row/column, or because new data has been set for
   * it.
   *
   * When \p role is Wt::StyleClassRole, the style class is appended
   * to any style already returned by the source model or set by
   * setNewRowData().
   *
   * By default there is no dirty indication.
   */
  void setDirtyIndication(int role, const boost::any& data);

  virtual WModelIndex mapFromSource(const WModelIndex& sourceIndex) const;
  virtual WModelIndex mapToSource(const WModelIndex& proxyIndex) const;

  virtual void setSourceModel(WAbstractItemModel *sourceModel);

  virtual int columnCount(const WModelIndex& parent = WModelIndex()) const;
  virtual int rowCount(const WModelIndex& parent = WModelIndex()) const;

  virtual WModelIndex parent(const WModelIndex& index) const;
  virtual WModelIndex index(int row, int column,
			    const WModelIndex& parent = WModelIndex()) const;

  using WAbstractItemModel::setData;
  using WAbstractItemModel::data;
  using WAbstractItemModel::setHeaderData;

  virtual boost::any data(const WModelIndex& index, int role = DisplayRole)
    const;

  /*! \brief Sets item data.
   *
   * The default implementation will copy Wt::EditRole data to
   * Wt::DisplayRole. You may want to specialize the model to provide
   * a more specialized editing behaviour.
   */
  virtual bool setData(const WModelIndex& index, const boost::any& value,
		       int role = EditRole);

  virtual WFlags<ItemFlag> flags(const WModelIndex& index) const;

  virtual boost::any headerData(int section,
				Orientation orientation = Horizontal,
				int role = DisplayRole) const;

  virtual bool insertRows(int row, int count,
			  const WModelIndex& parent = WModelIndex());

  virtual bool removeRows(int row, int count,
			  const WModelIndex& parent = WModelIndex());

  virtual bool insertColumns(int column, int count,
			     const WModelIndex& parent = WModelIndex());

  virtual bool removeColumns(int column, int count,
			     const WModelIndex& parent = WModelIndex());

  virtual void sort(int column, SortOrder order = AscendingOrder);

private:
  struct Cell {
    int row;
    int column;

    bool operator< (const Cell& other) const;

    Cell(int r, int c) : row(r), column(c) { }
  };

  typedef std::map<Cell, DataMap> ValueMap;

  /*
   * For every proxy parent, we keep the following info:
   */
  struct Item : public BaseItem {
    Item *insertedParent_;    // !=0 when parent was inserted

    ValueMap editedValues_;
    std::vector<int> removedRows_;      // sorted, proxy rows
    std::vector<int> insertedRows_;     // sorted, proxy rows
    std::vector<Item *> insertedItems_; // indexed by index in insertedRows_

    std::vector<int> removedColumns_;   // sorted, proxy columns
    std::vector<int> insertedColumns_;  // sorted, proxy columns

    Item(const WModelIndex& sourceIndex);
    Item(Item *insertedParent);
    virtual ~Item();
  };

  bool submitting_;
  std::map<int, DataMap> newRowData_;
  std::map<int, WFlags<ItemFlag> > newRowFlags_;
  int dirtyIndicationRole_;
  boost::any dirtyIndicationData_;

  std::vector<boost::signals::connection> modelConnections_;
  mutable ItemMap mappedIndexes_;

  void sourceColumnsAboutToBeInserted(const WModelIndex& parent,
				      int start, int end);
  void sourceColumnsInserted(const WModelIndex& parent, int start, int end);

  void sourceColumnsAboutToBeRemoved(const WModelIndex& parent,
				     int start, int end);
  void sourceColumnsRemoved(const WModelIndex& parent, int start, int end);

  void sourceRowsAboutToBeInserted(const WModelIndex& parent,
				   int start, int end);
  void sourceRowsInserted(const WModelIndex& parent, int start, int end);

  void sourceRowsAboutToBeRemoved(const WModelIndex& parent,
				  int start, int end);
  void sourceRowsRemoved(const WModelIndex& parent, int start, int end);

  void sourceDataChanged(const WModelIndex& topLeft,
			 const WModelIndex& bottomRight);

  void sourceHeaderDataChanged(Orientation orientation, int start, int end);

  void sourceLayoutAboutToBeChanged();
  void sourceLayoutChanged();

  Item *itemFromSourceIndex(const WModelIndex& sourceIndex,
			    bool autoCreate = true) const;
  Item *itemFromInsertedRow(Item *parentItem, const WModelIndex& index,
			    bool autoCreate = true) const;
  Item *parentItemFromIndex(const WModelIndex& index) const;
  Item *itemFromIndex(const WModelIndex& index, bool autoCreate = true) const;
  bool isRemoved(const WModelIndex& sourceIndex) const;
  int adjustedProxyRow(Item *item, int sourceRow) const;
  int adjustedSourceRow(Item *item, int proxyRow) const;
  int adjustedProxyColumn(Item *item, int sourceColumn) const;
  int adjustedSourceColumn(Item *item, int proxyColumn) const;
  int adjustedProxyIndex(int sourceIndex,
			 const std::vector<int>& ins,
			 const std::vector<int>& rem) const;
  int adjustedSourceIndex(int proxyIndex,
			  const std::vector<int>& ins,
			  const std::vector<int>& rem) const;

  void insertIndexes(Item *item,
		     std::vector<int>& ins, std::vector<Item *> *rowItems,
		     int index, int count);
  void removeIndexes(Item *item,
		     std::vector<int>& ins, std::vector<int>& rem,
		     std::vector<Item *>* rowItems,
		     int index, int count);

  void deleteItemsUnder(Item *item, int row);
  void shift(std::vector<int>& v, int row, int count);
  void shiftRows(ValueMap& v, int row, int count);
  void shiftRows(Item *item, int row, int count);
  void shiftColumns(ValueMap& v, int column, int count);
  void shiftColumns(Item *item, int column, int count);

  void resetMappings();

  boost::any indicateDirty(int role, const boost::any& value) const;
};

}

#endif // WBATCH_EDIT_PROXY_MODEL_H_
