/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Implementation of an XPath NodeSet
 */

#ifndef txNodeSet_h__
#define txNodeSet_h__

#include "txExprResult.h"
#include "nsError.h"
#include "txXPathNode.h"

class txNodeSet : public txAExprResult {
 public:
  /**
   * Creates a new empty NodeSet
   */
  explicit txNodeSet(txResultRecycler* aRecycler);

  /**
   * Creates a new NodeSet with one node.
   */
  txNodeSet(const txXPathNode& aNode, txResultRecycler* aRecycler);

  /**
   * Creates a new txNodeSet, copying the node references from the source
   * NodeSet.
   */
  txNodeSet(const txNodeSet& aSource, txResultRecycler* aRecycler);

  /**
   * Destructor for txNodeSet, deletes the nodes.
   */
  virtual ~txNodeSet();

  /**
   * Adds the specified txXPathNode to this NodeSet if it is not already
   * in this NodeSet. The node is inserted according to document order.
   *
   * @param  aNode the txXPathNode to add to the NodeSet
   * @return errorcode.
   */
  nsresult add(const txXPathNode& aNode);

  /**
   * Adds the nodes in specified NodeSet to this NodeSet. The resulting
   * NodeSet is sorted in document order and does not contain any duplicate
   * nodes.
   *
   * @param  aNodes the NodeSet to add, must be in document order.
   * @return errorcode.
   */
  nsresult add(const txNodeSet& aNodes);
  nsresult addAndTransfer(txNodeSet* aNodes);

  /**
   * Append API
   * These functions should be used with care.
   * They are intended to be used when the caller assures that the resulting
   * NodeSet remains in document order.
   * Abuse will break document order, and cause errors in the result.
   * These functions are significantly faster than the add API, as no
   * order info operations will be performed.
   */

  /**
   * Appends the specified Node to the end of this NodeSet
   * @param  aNode the Node to append to the NodeSet
   * @return errorcode.
   */
  nsresult append(const txXPathNode& aNode);

  /**
   * Appends the nodes in the specified NodeSet to the end of this NodeSet
   * @param  aNodes the NodeSet to append to the NodeSet
   * @return errorcode.
   */
  nsresult append(const txNodeSet& aNodes);

  /**
   * API to implement reverse axes in LocationStep.
   *
   * Before adding nodes to the nodeset for a reversed axis, call
   * setReverse(). This will make the append(aNode) and get() methods treat
   * the nodeset as required. Do only call append(aNode), get(), mark()
   * and sweep() while the nodeset is reversed.
   * Afterwards, call unsetReverse(). The nodes are stored in document
   * order internally.
   */
  void setReverse() { mDirection = -1; }
  void unsetReverse() { mDirection = 1; }

  /**
   * API to implement predicates in PredicateExpr
   *
   * mark(aIndex) marks the specified member of the nodeset.
   * sweep() clears all members of the nodeset that haven't been
   * marked before and clear the mMarks array.
   */
  nsresult mark(int32_t aIndex);
  nsresult sweep();

  /**
   * Removes all nodes from this nodeset
   */
  void clear();

  /**
   * Returns the index of the specified Node,
   * or -1 if the Node is not contained in the NodeSet
   * @param  aNode the Node to get the index for
   * @param  aStart index to start searching at
   * @return index of specified node or -1 if the node does not exist
   */
  int32_t indexOf(const txXPathNode& aNode, uint32_t aStart = 0) const;

  /**
   * Returns true if the specified Node is contained in the set.
   * @param  aNode the Node to search for
   * @return true if specified Node is contained in the NodeSet
   */
  bool contains(const txXPathNode& aNode) const { return indexOf(aNode) >= 0; }

  /**
   * Returns the Node at the specified node in this NodeSet.
   * @param  aIndex the node of the Node to return
   * @return Node at specified node
   */
  const txXPathNode& get(int32_t aIndex) const;

  /**
   * Returns true if there are no Nodes in the NodeSet.
   * @return true if there are no Nodes in the NodeSet.
   */
  bool isEmpty() const { return mStart ? mStart == mEnd : true; }

  /**
   * Returns the number of elements in the NodeSet
   * @return the number of elements in the NodeSet
   */
  int32_t size() const { return mStart ? mEnd - mStart : 0; }

  TX_DECL_EXPRRESULT

 private:
  /**
   * Ensure that this nodeset can take another aSize nodes.
   *
   * Changes mStart and mEnd as well as mBufferStart and mBufferEnd.
   */
  bool ensureGrowSize(int32_t aSize);

  /**
   * Finds position in the buffer where a node should be inserted
   * to keep the nodeset in document order. Searches the positions
   * aFirst-aLast, including aFirst, but not aLast.
   * @param  aNode   Node to find insert position for.
   * @param  aFirst  First item of the search range, included.
   * @param  aLast   Last item of the search range, excluded.
   * @param  aDupe   out-param. Will be set to true if the node already
   *                 exists in the NodeSet, false if it should be
   *                 inserted.
   * @return pointer where to insert the node. The node should be inserted
   *         before the given node. This value is always set, even if aNode
   *         already exists in the NodeSet
   */
  txXPathNode* findPosition(const txXPathNode& aNode, txXPathNode* aFirst,
                            txXPathNode* aLast, bool& aDupe) const;

  static void copyElements(txXPathNode* aDest, const txXPathNode* aStart,
                           const txXPathNode* aEnd);
  static void transferElements(txXPathNode* aDest, const txXPathNode* aStart,
                               const txXPathNode* aEnd);
  static void destroyElements(const txXPathNode* aStart,
                              const txXPathNode* aEnd) {
    while (aStart < aEnd) {
      aStart->~txXPathNode();
      ++aStart;
    }
  }

  using transferOp = void (*)(txXPathNode* aDest, const txXPathNode* aStart,
                              const txXPathNode* aEnd);
  using destroyOp = void (*)(const txXPathNode* aStart,
                             const txXPathNode* aEnd);
  nsresult add(const txNodeSet& aNodes, transferOp aTransfer,
               destroyOp aDestroy);

  txXPathNode *mStart, *mEnd, *mStartBuffer, *mEndBuffer;
  int32_t mDirection;
  // used for mark() and sweep() in predicates
  bool* mMarks;
};

#endif