/*
 * 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/completion.c#10 $
 */

#include "completion.h"

#include "logger.h"
#include "statusCodes.h"

static const char *VDO_COMPLETION_TYPE_NAMES[] = {
  // Keep UNSET_COMPLETION_TYPE at the top.
  "UNSET_COMPLETION_TYPE",

  // Keep the rest of these in sorted order. If you add or remove an entry,
  // be sure to update the corresponding list in completion.h.
  "ACTION_COMPLETION",
  "ADMIN_COMPLETION",
  "ASYNC_ACTION_CONTEXT",
  "BLOCK_ALLOCATOR_COMPLETION",
  "BLOCK_MAP_RECOVERY_COMPLETION",
  "CHECK_IDENTIFIER_COMPLETION",
  "EXTERNAL_COMPLETION",
  "FLUSH_NOTIFICATION_COMPLETION",
  "GENERATION_FLUSHED_COMPLETION",
  "HEARTBEAT_COMPLETION",
  "LOCK_COUNTER_COMPLETION",
  "PARTITION_COPY_COMPLETION",
  "READ_ONLY_MODE_COMPLETION",
  "READ_ONLY_REBUILD_COMPLETION",
  "RECOVERY_COMPLETION",
  "REFERENCE_COUNT_REBUILD_COMPLETION",
  "SLAB_SCRUBBER_COMPLETION",
  "SUB_TASK_COMPLETION",
  "TEST_COMPLETION",
  "VDO_COMMAND_COMPLETION",
  "VDO_COMMAND_SUB_COMPLETION",
  "VDO_EXTENT_COMPLETION",
  "VDO_PAGE_COMPLETION",
  "VIO_COMPLETION",
  "WRAPPING_COMPLETION",
};

/**********************************************************************/
void initializeCompletion(VDOCompletion         *completion,
                          VDOCompletionType      type,
                          PhysicalLayer         *layer)
{
  memset(completion, 0, sizeof(*completion));
  completion->layer = layer;
  completion->type  = type;
  resetCompletion(completion);
}

/**********************************************************************/
int initializeEnqueueableCompletion(VDOCompletion      *completion,
                                    VDOCompletionType   type,
                                    PhysicalLayer      *layer)
{
  initializeCompletion(completion, type, layer);
  return ((layer->createEnqueueable == NULL)
          ? VDO_SUCCESS : layer->createEnqueueable(completion));
}

/**********************************************************************/
void resetCompletion(VDOCompletion *completion)
{
  completion->result   = VDO_SUCCESS;
  completion->complete = false;
}

/**
 * Assert that a completion is not complete.
 *
 * @param completion The completion to check
 **/
static inline void assertIncomplete(VDOCompletion *completion)
{
  ASSERT_LOG_ONLY(!completion->complete, "completion is not complete");
}

/**********************************************************************/
void setCompletionResult(VDOCompletion *completion, int result)
{
  assertIncomplete(completion);
  if (completion->result == VDO_SUCCESS) {
    completion->result = result;
  }
}

/**
 * Check whether a completion's callback must be enqueued, or if it can be run
 * on the current thread. Side effect: clears the requeue flag if it is set,
 * so the caller MUST requeue if this returns true.
 *
 * @param completion  The completion whose callback is to be invoked
 *
 * @return <code>false</code> if the callback must be run on this thread
 *         <code>true</code>  if the callback must be enqueued
 **/
__attribute__((warn_unused_result))
static inline bool requiresEnqueue(VDOCompletion *completion)
{
  if (completion->requeue) {
    completion->requeue = false;
    return true;
  }

  ThreadID callbackThread = completion->callbackThreadID;
  return (callbackThread != completion->layer->getCurrentThreadID());
}

/**********************************************************************/
void invokeCallback(VDOCompletion *completion)
{
  if (requiresEnqueue(completion)) {
    if (completion->enqueueable != NULL) {
      completion->layer->enqueue(completion->enqueueable);
      return;
    }
    ASSERT_LOG_ONLY(false,
                    "non-enqueueable completion (type %s) on correct thread",
                    getCompletionTypeName(completion->type));
  }

  runCallback(completion);
}

/**********************************************************************/
void continueCompletion(VDOCompletion *completion, int result)
{
  setCompletionResult(completion, result);
  invokeCallback(completion);
}

/**********************************************************************/
void completeCompletion(VDOCompletion *completion)
{
  assertIncomplete(completion);
  completion->complete = true;
  if (completion->callback != NULL) {
    invokeCallback(completion);
  }
}

/**********************************************************************/
void releaseCompletion(VDOCompletion **completionPtr)
{
  VDOCompletion *completion = *completionPtr;
  if (completion == NULL) {
    return;
  }

  *completionPtr = NULL;
  completeCompletion(completion);
}

/**********************************************************************/
void releaseCompletionWithResult(VDOCompletion **completionPtr, int result)
{
  if (*completionPtr == NULL) {
    return;
  }

  setCompletionResult(*completionPtr, result);
  releaseCompletion(completionPtr);
}

/**********************************************************************/
void finishParentCallback(VDOCompletion *completion)
{
  finishCompletion((VDOCompletion *) completion->parent, completion->result);
}

/**********************************************************************/
void preserveErrorAndContinue(VDOCompletion *completion)
{
  if (completion->parent != NULL) {
    setCompletionResult(completion->parent, completion->result);
  }

  resetCompletion(completion);
  invokeCallback(completion);
}

/**********************************************************************/
const char *getCompletionTypeName(VDOCompletionType completionType)
{
  // Try to catch failures to update the array when the enum values change.
  STATIC_ASSERT(COUNT_OF(VDO_COMPLETION_TYPE_NAMES)
                == (MAX_COMPLETION_TYPE - UNSET_COMPLETION_TYPE));

  if (completionType >= MAX_COMPLETION_TYPE) {
    static char numeric[100];
    snprintf(numeric, 99, "%d (%#x)", completionType, completionType);
    return numeric;
  }

  return VDO_COMPLETION_TYPE_NAMES[completionType];
}

/**********************************************************************/
void destroyEnqueueable(VDOCompletion *completion)
{
  if ((completion == NULL) || (completion->layer == NULL)
      || (completion->layer->destroyEnqueueable == NULL)) {
    return;
  }

  completion->layer->destroyEnqueueable(&completion->enqueueable);
}

/**********************************************************************/
int assertCompletionType(VDOCompletionType actual,
                         VDOCompletionType expected)
{
  return ASSERT((expected == actual),
                "completion type is %s instead of %s",
                getCompletionTypeName(actual),
                getCompletionTypeName(expected));
}
