/*
 * Copyright (c) 2020 Red Hat, Inc.
 *
 * 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/adminState.h#17 $
 */

#ifndef ADMIN_STATE_H
#define ADMIN_STATE_H

#include "completion.h"
#include "types.h"

/**
 * The list of state types.
 **/
typedef enum {
  /** Normal operation, DataVIOs may be active */
  ADMIN_TYPE_NORMAL = 0,
  /**
   * Format: an operation for formatting a new VDO.
   **/
  ADMIN_TYPE_FORMAT,
  /**
   * Recover: a recovery operation.
   **/
  ADMIN_TYPE_RECOVER,
  /**
   * Rebuild: write data necessary for a full rebuild, drain outstanding I/O,
   *          and return to normal operation.
   **/
  ADMIN_TYPE_REBUILD,
  /**
   * Save: write all dirty metadata thereby restoring the VDO to a clean state,
   *       drain outstanding I/O, and become quiescent.
   **/
  ADMIN_TYPE_SAVE,
  /**
   * Scrub: load and/or save state necessary to scrub a slab.
   **/
  ADMIN_TYPE_SCRUB,
  /**
   * Suspend: write enough dirty metadata to perform resize transactions,
   *          drain outstanding I/O, and become quiescent.
   **/
  ADMIN_TYPE_SUSPEND,
  /**
   * Resume: return to normal from a quiescent state
   **/
  ADMIN_TYPE_RESUME,
  /** The mask for extracting the AdminType from and AdminStateCode */
  ADMIN_TYPE_MASK = 0xff,
} AdminType;


/**
 * The bit position of flags used to categorize states.
 **/
typedef enum {
  ADMIN_FLAG_BIT_START    = 8,
  /** Flag indicating that I/O is draining */
  ADMIN_FLAG_BIT_DRAINING = ADMIN_FLAG_BIT_START,
  /** Flag indicating a load operation */
  ADMIN_FLAG_BIT_LOADING,
  /** Flag indicating that the next state will be a quiescent state */
  ADMIN_FLAG_BIT_QUIESCING,
  /** Flag indicating that the state is quiescent */
  ADMIN_FLAG_BIT_QUIESCENT,
  /**
   * Flag indicating that an operation is in progress and so no other
   * operation may be started.
   **/
  ADMIN_FLAG_BIT_OPERATING,
} AdminFlagBit;

/**
 * The flags themselves.
 **/
typedef enum {
  ADMIN_FLAG_DRAINING  = (uint32_t) (1 << ADMIN_FLAG_BIT_DRAINING),
  ADMIN_FLAG_LOADING   = (uint32_t) (1 << ADMIN_FLAG_BIT_LOADING),
  ADMIN_FLAG_QUIESCING = (uint32_t) (1 << ADMIN_FLAG_BIT_QUIESCING),
  ADMIN_FLAG_QUIESCENT = (uint32_t) (1 << ADMIN_FLAG_BIT_QUIESCENT),
  ADMIN_FLAG_OPERATING = (uint32_t) (1 << ADMIN_FLAG_BIT_OPERATING),
} AdminFlag;

/**
 * The state codes.
 **/
typedef enum {
  ADMIN_STATE_NORMAL_OPERATION     = ADMIN_TYPE_NORMAL,
  ADMIN_STATE_OPERATING            = (ADMIN_TYPE_NORMAL
                                      | ADMIN_FLAG_OPERATING),
  ADMIN_STATE_FORMATTING           = (ADMIN_TYPE_FORMAT
                                      | ADMIN_FLAG_OPERATING
                                      | ADMIN_FLAG_LOADING),
  ADMIN_STATE_LOADING              = (ADMIN_TYPE_NORMAL
                                      | ADMIN_FLAG_OPERATING
                                      | ADMIN_FLAG_LOADING),
  ADMIN_STATE_LOADING_FOR_RECOVERY = (ADMIN_TYPE_RECOVER
                                      | ADMIN_FLAG_OPERATING
                                      | ADMIN_FLAG_LOADING),
  ADMIN_STATE_LOADING_FOR_REBUILD  = (ADMIN_TYPE_REBUILD
                                      | ADMIN_FLAG_OPERATING
                                      | ADMIN_FLAG_LOADING),
  ADMIN_STATE_WAITING_FOR_RECOVERY = (ADMIN_TYPE_RECOVER
                                      | ADMIN_FLAG_OPERATING),
  ADMIN_STATE_NEW                  = (ADMIN_TYPE_NORMAL
                                      | ADMIN_FLAG_QUIESCENT),
  ADMIN_STATE_RECOVERING           = (ADMIN_TYPE_RECOVER
                                      | ADMIN_FLAG_OPERATING
                                      | ADMIN_FLAG_DRAINING),
  ADMIN_STATE_REBUILDING           = (ADMIN_TYPE_REBUILD
                                      | ADMIN_FLAG_OPERATING
                                      | ADMIN_FLAG_DRAINING),
  ADMIN_STATE_SAVING               = (ADMIN_TYPE_SAVE
                                      | ADMIN_FLAG_OPERATING
                                      | ADMIN_FLAG_DRAINING
                                      | ADMIN_FLAG_QUIESCING),
  ADMIN_STATE_SAVED                = (ADMIN_TYPE_SAVE
                                      | ADMIN_FLAG_QUIESCENT),
  ADMIN_STATE_SCRUBBING            = (ADMIN_TYPE_SCRUB
                                      | ADMIN_FLAG_OPERATING
                                      | ADMIN_FLAG_DRAINING
                                      | ADMIN_FLAG_LOADING),
  ADMIN_STATE_SAVE_FOR_SCRUBBING   = (ADMIN_TYPE_SCRUB
                                      | ADMIN_FLAG_OPERATING
                                      | ADMIN_FLAG_DRAINING),
  ADMIN_STATE_SUSPENDING           = (ADMIN_TYPE_SUSPEND
                                      | ADMIN_FLAG_OPERATING
                                      | ADMIN_FLAG_DRAINING
                                      | ADMIN_FLAG_QUIESCING),
  ADMIN_STATE_SUSPENDED            = (ADMIN_TYPE_SUSPEND
                                      | ADMIN_FLAG_QUIESCENT),
  ADMIN_STATE_SUSPENDED_OPERATION  = (ADMIN_TYPE_SUSPEND
                                      | ADMIN_FLAG_OPERATING
                                      | ADMIN_FLAG_QUIESCENT),
  ADMIN_STATE_RESUMING             = (ADMIN_TYPE_RESUME
                                      | ADMIN_FLAG_OPERATING
                                      | ADMIN_FLAG_QUIESCENT),
} AdminStateCode;

typedef struct {
  /** The current administrative state */
  AdminStateCode  state;
  /** The next administrative state (when the current operation finishes */
  AdminStateCode  nextState;
  /** A completion waiting on a state change */
  VDOCompletion  *waiter;
  /** Whether an operation is being initiated */
  bool            starting;
  /** Whether an operation has completed in the initiator */
  bool            complete;
} AdminState;

/**
 * A method to be called once an admin operation may be initiated.
 **/
typedef void AdminInitiator(AdminState *state);

/**
 * Get the name of an AdminStateCode for logging purposes.
 *
 * @param code  The AdminStateCode
 *
 * @return The name of the state's code
 **/
const char *getAdminStateCodeName(AdminStateCode code)
  __attribute__((warn_unused_result));

/**
 * Get the name of an AdminState's code for logging purposes.
 *
 * @param state  The AdminState
 *
 * @return The name of the state's code
 **/
const char *getAdminStateName(const AdminState *state)
  __attribute__((warn_unused_result));

/**
 * Check whether an AdminState is in normal operation.
 *
 * @param state  The AdminState to query
 *
 * @return <code>true</code> if the state is normal
 **/
__attribute__((warn_unused_result))
static inline bool isNormal(AdminState *state)
{
  return ((state->state & ADMIN_TYPE_MASK) == ADMIN_TYPE_NORMAL);
}

/**
 * Check whether an AdminStateCode is an operation.
 *
 * @param code  The code to check
 *
 * @return <code>true</code> if the code is an operation
 **/
__attribute__((warn_unused_result))
static inline bool isOperation(AdminStateCode code)
{
  return ((code & ADMIN_FLAG_OPERATING) == ADMIN_FLAG_OPERATING);
}

/**
 * Check whether an AdminState is operating.
 *
 * @param state  The AdminState to query
 *
 * @return <code>true</code> if the state is operating
 **/
__attribute__((warn_unused_result))
static inline bool isOperating(AdminState *state)
{
  return isOperation(state->state);
}

/**
 * Check whether an AdminState is suspending.
 *
 * @param state  The AdminState to query
 *
 * @return <code>true</code> if the state is suspending
 **/
__attribute__((warn_unused_result))
static inline bool isSuspending(AdminState *state)
{
  return (state->state == ADMIN_STATE_SUSPENDING);
}

/**
 * Check whether an AdminState is suspended.
 *
 * @param state  The AdminState to query
 *
 * @return <code>true</code> if the state is suspended
 **/
__attribute__((warn_unused_result))
static inline bool isSuspended(AdminState *state)
{
  return (state->state == ADMIN_STATE_SUSPENDED);
}

/**
 * Check whether an AdminState is saving.
 *
 * @param state  The AdminState to query
 *
 * @return <code>true</code> if the state is saving
 **/
__attribute__((warn_unused_result))
static inline bool isSaving(AdminState *state)
{
  return (state->state == ADMIN_STATE_SAVING);
}

/**
 * Check whether an AdminState is saved.
 *
 * @param state  The AdminState to query
 *
 * @return <code>true</code> if the state is saved
 **/
__attribute__((warn_unused_result))
static inline bool isSaved(AdminState *state)
{
  return (state->state == ADMIN_STATE_SAVED);
}

/**
 * Check whether an AdminStateCode is a drain operation.
 *
 * @param code  The AdminStateCode to check
 *
 * @return <code>true</code> if the code is for a drain operation
 **/
__attribute__((warn_unused_result))
static inline bool isDrainOperation(AdminStateCode code)
{
  return ((code & ADMIN_FLAG_DRAINING) == ADMIN_FLAG_DRAINING);
}

/**
 * Check whether an AdminState is draining.
 *
 * @param state  The AdminState to query
 *
 * @return <code>true</code> if the state is draining
 **/
__attribute__((warn_unused_result))
static inline bool isDraining(AdminState *state)
{
  return isDrainOperation(state->state);
}

/**
 * Check whether an AdminStateCode is a load operation.
 *
 * @param code  The AdminStateCode to check
 *
 * @return <code>true</code> if the code is for a load operation
 **/
__attribute__((warn_unused_result))
static inline bool isLoadOperation(AdminStateCode code)
{
  return ((code & ADMIN_FLAG_LOADING) == ADMIN_FLAG_LOADING);
}

/**
 * Check whether an AdminState is loading.
 *
 * @param state  The AdminState to query
 *
 * @return <code>true</code> if the state is loading
 **/
__attribute__((warn_unused_result))
static inline bool isLoading(AdminState *state)
{
  return isLoadOperation(state->state);
}

/**
 * Check whether an AdminStateCode is a resume operation.
 *
 * @param code  The AdminStateCode to check
 *
 * @return <code>true</code> if the code is for a resume operation
 **/
__attribute__((warn_unused_result))
static inline bool isResumeOperation(AdminStateCode code)
{
  return ((code & ADMIN_TYPE_MASK) == ADMIN_TYPE_RESUME);
}

/**
 * Check whether an AdminState is resumeing.
 *
 * @param state  The AdminState to query
 *
 * @return <code>true</code> if the state is resumeing
 **/
__attribute__((warn_unused_result))
static inline bool isResuming(AdminState *state)
{
  return isResumeOperation(state->state);
}

/**
 * Check whether an AdminState is doing a clean load.
 *
 * @param state  The AdminState to query
 *
 * @return <code>true</code> if the state is a clean load
 **/
__attribute__((warn_unused_result))
static inline bool isCleanLoad(AdminState *state)
{
  return ((state->state == ADMIN_STATE_FORMATTING)
          || (state->state == ADMIN_STATE_LOADING));
}

/**
 * Check whether an AdminStateCode is quiescing.
 *
 * param code  The AdminStateCode to check
 *
 * @return <code>true</code> is the state is quiescing
 **/
__attribute__((warn_unused_result))
static inline bool isQuiescingCode(AdminStateCode code)
{
  return ((code & ADMIN_FLAG_QUIESCING) == ADMIN_FLAG_QUIESCING);
}

/**
 * Check whether an AdminState is quiescing.
 *
 * @param state  The AdminState to check
 *
 * @return <code>true</code> if the state is quiescing
 **/
__attribute__((warn_unused_result))
static inline bool isQuiescing(AdminState *state)
{
  return isQuiescingCode(state->state);
}

/**
 * Check where an AdminStateCode is quiescent.
 *
 * param code  The AdminStateCode to check
 *
 * @return <code>true</code> is the state is quiescent
 **/
__attribute__((warn_unused_result))
static inline bool isQuiescentCode(AdminStateCode code)
{
  return ((code & ADMIN_FLAG_QUIESCENT) == ADMIN_FLAG_QUIESCENT);
}

/**
 * Check whether an AdminState is quiescent.
 *
 * @param state  The AdminState to query
 *
 * @return <code>true</code> is the state is quiescent
 **/
__attribute__((warn_unused_result))
static inline bool isQuiescent(AdminState *state)
{
  return isQuiescentCode(state->state);
}

/**
 * Check whether an AdminStateCode is a quiescent operation.
 *
 * @param code  The code to check
 *
 * @return <code>true</code> if the code is a quiescent operation
 **/
__attribute__((warn_unused_result))
static inline bool isQuiescentOperation(AdminStateCode code)
{
  return (isQuiescentCode(code) && isOperation(code));
}

/**
 * Check that an operation is a drain.
 *
 * @param operation  The operation to check
 * @param waiter     The completion to finish with an error if the operation is
 *                   not a drain
 *
 * @return <code>true</code> if the specified operation is a drain
 **/
bool assertDrainOperation(AdminStateCode operation, VDOCompletion *waiter)
  __attribute__((warn_unused_result));

/**
 * Initiate a drain operation if the current state permits it.
 *
 * @param state      The AdminState
 * @param operation  The type of drain to initiate
 * @param waiter     The completion to notify when the drain is complete; may
 *                   be NULL
 * @param initiator  The AdminInitiator to call if the operation may begin; may
 *                   be NULL
 *
 * @return <code>true</code> if the drain was initiated, if not the waiter
 *         will be notified
 **/
bool startDraining(AdminState     *state,
                   AdminStateCode  operation,
                   VDOCompletion  *waiter,
                   AdminInitiator *initiator);

/**
 * Finish a drain operation if one was in progress.
 *
 * @param state  The AdminState to query
 *
 * @return <code>true</code> if the state was draining; will notify the waiter
 *         if so
 **/
bool finishDraining(AdminState *state);

/**
 * Finish a drain operation with a status code.
 *
 * @param state   The AdminState to query
 * @param result  The result of the drain operation
 *
 * @return <code>true</code> if the state was draining; will notify the
 *         waiter if so
 **/
bool finishDrainingWithResult(AdminState *state, int result);

/**
 * Check that an operation is a load.
 *
 * @param operation  The operation to check
 * @param waiter     The completion to finish with an error if the operation is
 *                   not a load
 *
 * @return <code>true</code> if the specified operation is a load
 **/
bool assertLoadOperation(AdminStateCode operation, VDOCompletion *waiter)
  __attribute__((warn_unused_result));

/**
 * Initiate a load operation if the current state permits it.
 *
 * @param state      The AdminState
 * @param operation  The type of load to initiate
 * @param waiter     The completion to notify when the load is complete; may be
 *                   NULL
 * @param initiator  The AdminInitiator to call if the operation may begin; may
 *                   be NULL
 *
 * @return <code>true</code> if the load was initiated, if not the waiter
 *         will be notified
 **/
bool startLoading(AdminState     *state,
                  AdminStateCode  operation,
                  VDOCompletion  *waiter,
                  AdminInitiator *initiator);

/**
 * Finish a load operation if one was in progress.
 *
 * @param state  The AdminState to query
 *
 * @return <code>true</code> if the state was loading; will notify the waiter
 *         if so
 **/
bool finishLoading(AdminState *state);

/**
 * Finish a load operation with a status code.
 *
 * @param state   The AdminState to query
 * @param result  The result of the load operation
 *
 * @return <code>true</code> if the state was loading; will notify the
 *         waiter if so
 **/
bool finishLoadingWithResult(AdminState *state, int result);

/**
 * Check whether an AdminStateCode is a resume operation.
 *
 * @param operation  The operation to check
 * @param waiter     The completion to notify if the operation is not a resume
 *                   operation; may be NULL
 *
 * @return <code>true</code> if the code is a resume operation
 **/
bool assertResumeOperation(AdminStateCode operation, VDOCompletion *waiter);

/**
 * Initiate a resume operation if the current state permits it.
 *
 * @param state      The AdminState
 * @param operation  The type of resume to start
 * @param waiter     The completion to notify when the resume is complete; may
 *                   be NULL
 * @param initiator  The AdminInitiator to call if the operation may begin; may
 *                   be NULL
 *
 * @return <code>true</code> if the resume was initiated, if not the waiter
 *         will be notified
 **/
bool startResuming(AdminState     *state,
                   AdminStateCode  operation,
                   VDOCompletion  *waiter,
                   AdminInitiator *initiator);

/**
 * Finish a resume operation if one was in progress.
 *
 * @param state  The AdminState to query
 *
 * @return <code>true</code> if the state was resuming; will notify the waiter
 *         if so
 **/
bool finishResuming(AdminState *state);

/**
 * Finish a resume operation with a status code.
 *
 * @param state   The AdminState to query
 * @param result  The result of the resume operation
 *
 * @return <code>true</code> if the state was resuming; will notify the
 *         waiter if so
 **/
bool finishResumingWithResult(AdminState *state, int result);

/**
 * Change the state to normal operation if the current state is quiescent.
 *
 * @param state  The AdminState to resume
 *
 * @return VDO_SUCCESS if the state resumed, VDO_INVALID_ADMIN_STATE otherwise
 **/
int resumeIfQuiescent(AdminState *state);

/**
 * Attempt to start an operation.
 *
 * @param state      the AdminState
 * @param operation  the operation to start
 *
 * @return VDO_SUCCESS             if the operation was started
 *         VDO_INVALID_ADMIN_STATE if not
 **/
int startOperation(AdminState *state, AdminStateCode operation);

/**
 * Attempt to start an operation.
 *
 * @param state      the AdminState
 * @param operation  the operation to start
 * @param waiter     the completion to notify when the operation completes or
 *                   fails to start; may be NULL
 * @param initiator  The AdminInitiator to call if the operation may begin; may
 *                   be NULL
 *
 * @return <code>true</code> if the operation was started
 **/
bool startOperationWithWaiter(AdminState     *state,
                              AdminStateCode  operation,
                              VDOCompletion  *waiter,
                              AdminInitiator *initiator);

/**
 * Finish the current operation. Will notify the operation waiter if there is
 * one. This method should be used for operations started with
 * startOperation(). For operations which were started with startDraining(),
 * use finishDraining() instead.
 *
 * @param state  The state whose operation is to be finished
 *
 * @return <code>true</code> if there was an operation to finish
 **/
bool finishOperation(AdminState *state);

/**
 * Finish the current operation with a status code. Will notify the operation
 * waiter if there is one.
 *
 * @param state   The state whose operation is to be finished
 * @param result  The result of the operation
 **/
bool finishOperationWithResult(AdminState *state, int result);

/**
 * Set a result for the current operation.
 *
 * @param state  the AdminState
 * @param result the result to set; if there is no waiter, this is a no-op
 **/
static inline void setOperationResult(AdminState *state, int result)
{
  if (state->waiter != NULL) {
    setCompletionResult(state->waiter, result);
  }
}

#endif // ADMIN_STATE_H
