/*
 * Copyright Red Hat
 *
 * 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; either version 2
 * of the License, or (at your option) any later version.
 * 
 * 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 Street, Fifth Floor, Boston, MA
 * 02110-1301, USA. 
 *
 * $Id: //eng/vdo-releases/aluminum/src/c++/vdo/base/refCountsInternals.h#4 $
 */

#ifndef REF_COUNTS_INTERNALS_H
#define REF_COUNTS_INTERNALS_H

#include "refCounts.h"

#include "journalPoint.h"
#include "referenceBlock.h"
#include "slab.h"
#include "blockAllocatorInternals.h"
#include "waitQueue.h"

/**
 * Represents the possible status of a block.
 **/
typedef enum referenceStatus {
  RS_FREE,        // this block is free
  RS_SINGLE,      // this block is singly-referenced
  RS_SHARED,      // this block is shared
  RS_PROVISIONAL  // this block is provisionally allocated
} ReferenceStatus;

/**
 * The SearchCursor represents the saved position of a free block search.
 **/
typedef struct searchCursor {
  /** The reference block containing the current search index */
  ReferenceBlock      *block;
  /** The position at which to start searching for the next free counter */
  SlabBlockNumber      index;
  /** The position just past the last valid counter in the current block */
  SlabBlockNumber      endIndex;

  /** A pointer to the first reference block in the slab */
  ReferenceBlock      *firstBlock;
  /** A pointer to the last reference block in the slab */
  ReferenceBlock      *lastBlock;
} SearchCursor;

/*
 * RefCounts structure
 *
 * A reference count is maintained for each PhysicalBlockNumber.  The vast
 * majority of blocks have a very small reference count (usually 0 or 1).
 * For references less than or equal to MAXIMUM_REFS (254) the reference count
 * is stored in counters[pbn].
 *
 */
struct refCounts {
  /** The slab of this reference block */
  Slab                     *slab;

  /** The size of the counters array */
  uint32_t                  blockCount;
  /** The number of free blocks */
  uint32_t                  freeBlocks;
  /** The array of reference counts */
  ReferenceCount           *counters; // use ALLOCATE to align data ptr

  /** The saved block pointer and array indexes for the free block search */
  SearchCursor              searchCursor;

  /** A list of the dirty blocks waiting to be written out */
  WaitQueue                 dirtyBlocks;
  /** The number of blocks which are currently writing */
  size_t                    activeCount;

  /** A waiter object for updating the slab summary */
  Waiter                    slabSummaryWaiter;
  /** Whether slab summary update is in progress */
  bool                      updatingSlabSummary;

  /** The notifier for read-only mode */
  ReadOnlyNotifier         *readOnlyNotifier;
  /** The refcount statistics, shared by all refcounts in our physical zone */
  AtomicRefCountStatistics *statistics;
  /** The layer PBN for the first ReferenceBlock */
  PhysicalBlockNumber       origin;
  /** The latest slab journal entry this RefCounts has been updated with */
  JournalPoint              slabJournalPoint;

  /** The number of reference count blocks */
  uint32_t                  referenceBlockCount;
  /** reference count block array */
  ReferenceBlock            blocks[];
};

/**
 * Convert a reference count to a reference status.
 *
 * @param count The count to convert
 *
 * @return  The appropriate reference status
 **/
__attribute__((warn_unused_result))
ReferenceStatus referenceCountToStatus(ReferenceCount count);

/**
 * Convert a generic VDOCompletion to a RefCounts.
 *
 * @param completion The completion to convert
 *
 * @return The completion as a RefCounts
 **/
RefCounts *asRefCounts(VDOCompletion *completion)
  __attribute__((warn_unused_result));

/**
 * Get the reference block that covers the given block index (exposed for
 * testing).
 *
 * @param refCounts  The refcounts object
 * @param index      The block index
 **/
ReferenceBlock *getReferenceBlock(RefCounts *refCounts, SlabBlockNumber index)
  __attribute__((warn_unused_result));

/**
 * Find the reference counters for a given block (exposed for testing).
 *
 * @param block  The ReferenceBlock in question
 *
 * @return A pointer to the reference counters for this block
 **/
ReferenceCount *getReferenceCountersForBlock(ReferenceBlock *block)
  __attribute__((warn_unused_result));

/**
 * Copy data from a reference block to a buffer ready to be written out
 * (exposed for testing).
 *
 * @param block   The block to copy
 * @param buffer  The char buffer to fill with the packed block
 **/
void packReferenceBlock(ReferenceBlock *block, void *buffer);

/**
 * Get the reference status of a block. Exposed only for unit testing.
 *
 * @param [in]  refCounts   The refcounts object
 * @param [in]  pbn         The physical block number
 * @param [out] statusPtr   Where to put the status of the block
 *
 * @return                  A success or error code, specifically:
 *                          VDO_OUT_OF_RANGE if the pbn is out of range.
 **/
int getReferenceStatus(RefCounts           *refCounts,
                       PhysicalBlockNumber  pbn,
                       ReferenceStatus     *statusPtr)
  __attribute__((warn_unused_result));

/**
 * Find the first block with a reference count of zero in the specified range
 * of reference counter indexes. Exposed for unit testing.
 *
 * @param [in]  refCounts   The reference counters to scan
 * @param [in]  startIndex  The array index at which to start scanning
 *                          (included in the scan)
 * @param [in]  endIndex    The array index at which to stop scanning
 *                          (excluded from the scan)
 * @param [out] indexPtr    A pointer to hold the array index of the free block
 *
 * @return true if a free block was found in the specified range
 **/
bool findFreeBlock(const RefCounts *refCounts,
                   SlabBlockNumber  startIndex,
                   SlabBlockNumber  endIndex,
                   SlabBlockNumber *indexPtr)
  __attribute__((warn_unused_result));

/**
 * Request a RefCounts save its oldest dirty block asynchronously.
 *
 * @param refCounts  The RefCounts object to notify
 **/
void saveOldestReferenceBlock(RefCounts *refCounts);

/**
 * Reset all reference counts back to RS_FREE.
 *
 * @param refCounts   The reference counters to reset
 **/
void resetReferenceCounts(RefCounts *refCounts);

#endif // REF_COUNTS_INTERNALS_H
