/*
 * 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/uds-releases/jasper/src/uds/indexComponent.c#8 $
 */

#include "indexComponent.h"

#include "compiler.h"
#include "errors.h"
#include "indexLayout.h"
#include "indexState.h"
#include "logger.h"
#include "memoryAlloc.h"
#include "permassert.h"
#include "typeDefs.h"

/*****************************************************************************/
int makeIndexComponent(IndexState                *state,
                       const IndexComponentInfo  *info,
                       unsigned int               zoneCount,
                       void                      *data,
                       void                      *context,
                       IndexComponent           **componentPtr)
{
  if ((info == NULL) || (info->name == NULL)) {
    return logErrorWithStringError(UDS_INVALID_ARGUMENT,
                                   "invalid component or directory specified");
  }
  if (info->loader == NULL) {
    return logErrorWithStringError(UDS_INVALID_ARGUMENT,
                                   "no .loader function specified "
                                   "for component %s",
                                   info->name);
  }
  if ((info->saver == NULL) && (info->incremental == NULL)) {
    return logErrorWithStringError(UDS_INVALID_ARGUMENT,
                                   "neither .saver function nor .incremental "
                                   "function specified for component %s",
                                   info->name);
  }

  IndexComponent *component = NULL;
  int result = ALLOCATE(1, IndexComponent, "index component", &component);
  if (result != UDS_SUCCESS) {
    return result;
  }

  component->componentData = data;
  component->context       = context;
  component->info          = info;
  component->numZones      = info->multiZone ? zoneCount : 1;
  component->state         = state;
  component->writeZones    = NULL;
  *componentPtr = component;
  return UDS_SUCCESS;
}

/*****************************************************************************/
static void freeWriteZones(IndexComponent *component)
{
  if (component->writeZones != NULL) {
    unsigned int z;
    for (z = 0; z < component->numZones; ++z) {
      WriteZone *wz = component->writeZones[z];
      if (wz == NULL) {
        continue;
      }
      freeBufferedWriter(wz->writer);
      FREE(wz);
    }
    FREE(component->writeZones);
    component->writeZones = NULL;
  }
}

/*****************************************************************************/
void freeIndexComponent(IndexComponent **componentPtr)
{
  if (componentPtr == NULL) {
    return;
  }
  IndexComponent *component = *componentPtr;
  if (component == NULL) {
    return;
  }
  *componentPtr = NULL;

  freeWriteZones(component);
  FREE(component);
}

/**
 * Destroy, deallocate, and expunge a read portal.
 *
 * @param readPortal     the readzone array
 **/
static void freeReadPortal(ReadPortal *readPortal)
{
  if (readPortal == NULL) {
    return;
  }
  unsigned int z;
  for (z = 0; z < readPortal->zones; ++z) {
    if (readPortal->readers[z] != NULL) {
      freeBufferedReader(readPortal->readers[z]);
    }
  }
  FREE(readPortal->readers);
  FREE(readPortal);
}

/*****************************************************************************/
int getBufferedReaderForPortal(ReadPortal      *portal,
                               unsigned int     part,
                               BufferedReader **readerPtr)
{
  if (part >= portal->zones) {
    return logErrorWithStringError(UDS_INVALID_ARGUMENT,
                                   "%s: cannot access zone %u of %u",
                                   __func__, part, portal->zones);
  }
  IndexComponent *component = portal->component;
  if (component->info->ioStorage && (portal->readers[part] == NULL)) {
    int result = openStateBufferedReader(component->state,
                                         component->info->kind, part,
                                         &portal->readers[part]);
    if (result != UDS_SUCCESS) {
      return logErrorWithStringError(result,
                                     "%s: cannot make buffered reader "
                                     "for zone %u", __func__, part);
    }
  }
  *readerPtr = portal->readers[part];
  return UDS_SUCCESS;
}

/*****************************************************************************/
int readIndexComponent(IndexComponent *component)
{
  ReadPortal *portal;
  int result = ALLOCATE(1, ReadPortal, "index component read portal", &portal);
  if (result != UDS_SUCCESS) {
    return result;
  }
  int readZones = component->state->loadZones;
  result = ALLOCATE(readZones, BufferedReader *, "read zone buffered readers",
                    &portal->readers);
  if (result != UDS_SUCCESS) {
    FREE(portal);
    return result;
  }

  portal->component = component;
  portal->zones = readZones;
  result = (*component->info->loader)(portal);
  freeReadPortal(portal);
  return result;
}

/**
 * Determine the writeZone structure for the specified component and zone.
 *
 * @param [in]  component      the index component
 * @param [in]  zone           the zone number
 * @param [out] writeZonePtr   the resulting write zone instance
 *
 * @return UDS_SUCCESS or an error code
 **/
static int resolveWriteZone(const IndexComponent  *component,
                            unsigned int           zone,
                            WriteZone            **writeZonePtr)
{
  int result = ASSERT(writeZonePtr != NULL,
                      "output parameter is null");
  if (result != UDS_SUCCESS) {
    return result;
  }

  if (component->writeZones == NULL) {
    return logErrorWithStringError(UDS_BAD_STATE,
                                   "cannot resolve index component write zone:"
                                   " not allocated");
  }

  if (zone >= component->numZones) {
    return logErrorWithStringError(UDS_INVALID_ARGUMENT,
                                   "cannot resolve index component write zone:"
                                   " zone out of range");
  }
  *writeZonePtr = component->writeZones[zone];
  return UDS_SUCCESS;
}

/**
 * Non-incremental save function used to emulate a regular save
 * using an incremental save function as a basis.
 *
 * @param component    the index component
 * @param writer       the buffered writer
 * @param zone         the zone number
 *
 * @return UDS_SUCCESS or an error code
 **/
static int indexComponentSaverIncrementalWrapper(IndexComponent *component,
                                                 BufferedWriter *writer,
                                                 unsigned int    zone)
{
  IncrementalWriter incrFunc  = component->info->incremental;
  bool              completed = false;

  int result = (*incrFunc)(component, writer, zone, IWC_START, &completed);
  if (result != UDS_SUCCESS) {
    return result;
  }

  if (!completed) {
    result = (*incrFunc)(component, writer, zone, IWC_FINISH, &completed);
    if (result != UDS_SUCCESS) {
      return result;
    }
  }

  result = flushBufferedWriter(writer);
  if (result != UDS_SUCCESS) {
    return result;
  }

  return UDS_SUCCESS;
}

/**
 * Specify that writing to a specific zone file has finished.
 *
 * If a syncer has been registered with the index component, the file
 * descriptor will be enqueued upon it for fsyncing and closing.
 * If not, or if the enqueue fails, the file will be fsynced and closed
 * immediately.
 *
 * @param writeZone    the index component write zone
 *
 * @return UDS_SUCCESS or an error code
 **/
static int doneWithZone(WriteZone *writeZone)
{
  const IndexComponent *component = writeZone->component;
  if (writeZone->writer != NULL) {
    int result = flushBufferedWriter(writeZone->writer);
    if (result != UDS_SUCCESS) {
      return logErrorWithStringError(result,
                                     "cannot flush buffered writer for "
                                     "%s component (zone %u)",
                                     component->info->name, writeZone->zone);
    }
  }
  return UDS_SUCCESS;
}

/**
 * Construct the array of WriteZone instances for this component.
 *
 * @param component    the index component
 *
 * @return UDS_SUCCESS or an error code
 *
 * If this is a multizone component, each zone will be fully defined,
 * otherwise zone 0 stands in for the single state file.
 **/
static int makeWriteZones(IndexComponent *component)
{
  unsigned int z;
  if (component->writeZones != NULL) {
    // just reinitialize states
    for (z = 0; z < component->numZones; ++z) {
      WriteZone *wz = component->writeZones[z];
      wz->phase = IWC_IDLE;
    }
    return UDS_SUCCESS;
  }

  int result = ALLOCATE(component->numZones, WriteZone *,
                        "index component write zones", &component->writeZones);
  if (result != UDS_SUCCESS) {
    return result;
  }

  for (z = 0; z < component->numZones; ++z) {
    result = ALLOCATE(1, WriteZone, "plain write zone",
                      &component->writeZones[z]);
    if (result != UDS_SUCCESS) {
      freeWriteZones(component);
      return result;
    }
    *component->writeZones[z] = (WriteZone) {
      .component = component,
      .phase     = IWC_IDLE,
      .zone      = z,
    };
  }
  return UDS_SUCCESS;
}

/*****************************************************************************/
static int openBufferedWriters(IndexComponent *component)
{
  int result = UDS_SUCCESS;
  WriteZone **wzp;
  for (wzp = component->writeZones;
       wzp < component->writeZones + component->numZones;
       ++wzp) {
    WriteZone *wz = *wzp;
    wz->phase = IWC_START;

    result = ASSERT(wz->writer == NULL, "write zone writer already exists");
    if (result != UDS_SUCCESS) {
      return result;
    }

    if (component->info->ioStorage) {
      int result = openStateBufferedWriter(component->state,
                                           component->info->kind, wz->zone,
                                           &wz->writer);
      if (result != UDS_SUCCESS) {
        return result;
      }
    }
  }
  return UDS_SUCCESS;
}

/*****************************************************************************/
static int startIndexComponentSave(IndexComponent *component)
{
  int result = makeWriteZones(component);
  if (result != UDS_SUCCESS) {
    return result;
  }

  result = openBufferedWriters(component);
  if (result != UDS_SUCCESS) {
    return result;
  }

  return UDS_SUCCESS;
}

/*****************************************************************************/
int startIndexComponentIncrementalSave(IndexComponent *component)
{
  return startIndexComponentSave(component);
}

/*****************************************************************************/
int writeIndexComponent(IndexComponent *component)
{
  Saver saver = component->info->saver;
  if ((saver == NULL) && (component->info->incremental != NULL)) {
    saver = indexComponentSaverIncrementalWrapper;
  }

  int result = startIndexComponentSave(component);
  if (result != UDS_SUCCESS) {
    return result;
  }

  unsigned int z;
  for (z = 0; z < component->numZones; ++z) {
    WriteZone *writeZone = component->writeZones[z];

    result = (*saver)(component, writeZone->writer, z);
    if (result != UDS_SUCCESS) {
      break;
    }

    result = doneWithZone(writeZone);
    if (result != UDS_SUCCESS) {
      break;
    }

    freeBufferedWriter(writeZone->writer);
    writeZone->writer = NULL;
  }

  if (result != UDS_SUCCESS) {
    freeWriteZones(component);
    return logErrorWithStringError(result, "index component write failed");
  }

  return UDS_SUCCESS;
}

/**
 * Close a specific buffered writer in a component write zone.
 *
 * @param writeZone    the write zone
 *
 * @return UDS_SUCCESS or an error code
 *
 * @note closing a buffered writer causes its file descriptor to be
 *       passed to doneWithZone
 **/
static int closeBufferedWriter(WriteZone *writeZone)
{
  if (writeZone->writer == NULL) {
    return UDS_SUCCESS;
  }

  int result = doneWithZone(writeZone);
  freeBufferedWriter(writeZone->writer);
  writeZone->writer = NULL;

  return result;
}

/**
 * Faux incremental saver function for index components which only define
 * a simple saver.  Conforms to IncrementalWriter signature.
 *
 * @param [in]  component      the index component
 * @param [in]  writer         the buffered writer that does the output
 * @param [in]  zone           the zone number
 * @param [in]  command        the incremental writer command
 * @param [out] completed      if non-NULL, set to whether the save is complete
 *
 * @return UDS_SUCCESS or an error code
 *
 * @note This wrapper always calls the non-incremental saver when
 *       the IWC_START command is issued, and always reports that
 *       the save is complete unless the saver failed.
 **/
static int wrapSaverAsIncremental(IndexComponent           *component,
                                  BufferedWriter           *writer,
                                  unsigned int              zone,
                                  IncrementalWriterCommand  command,
                                  bool                     *completed)
{
  int result = UDS_SUCCESS;

  if ((command >= IWC_START) && (command <= IWC_FINISH)) {
    result = (*component->info->saver)(component, writer, zone);
    if ((result == UDS_SUCCESS) && (writer != NULL)) {
      noteBufferedWriterUsed(writer);
    }
  }
  if ((result == UDS_SUCCESS) && (completed != NULL)) {
    *completed = true;
  }
  return result;
}

/**
 * Return the appropriate incremental writer function depending on
 * the component's type and whether this is the first zone.
 *
 * @param component    the index component
 *
 * @return the correct IncrementalWriter function to use, or
 *         NULL signifying no progress can be made at this time.
 **/
static IncrementalWriter getIncrementalWriter(IndexComponent *component)
{
  IncrementalWriter incrFunc = component->info->incremental;

  if (incrFunc == NULL) {
    incrFunc = &wrapSaverAsIncremental;
  }

  return incrFunc;
}

/*****************************************************************************/
int performIndexComponentZoneSave(IndexComponent   *component,
                                  unsigned int      zone,
                                  CompletionStatus *completed)
{
  CompletionStatus comp = CS_NOT_COMPLETED;

  WriteZone *wz = NULL;
  int result = resolveWriteZone(component, zone, &wz);
  if (result != UDS_SUCCESS) {
    return result;
  }

  if (wz->phase == IWC_IDLE) {
    comp = CS_COMPLETED_PREVIOUSLY;
  } else if (wz->phase == IWC_DONE) {
    comp = CS_JUST_COMPLETED;
    wz->phase = IWC_IDLE;
  } else if (!component->info->chapterSync) {
    bool done = false;
    IncrementalWriter incrFunc = getIncrementalWriter(component);
    int result = (*incrFunc)(component, wz->writer, zone, wz->phase, &done);
    if (result != UDS_SUCCESS) {
      if (wz->phase == IWC_ABORT) {
        wz->phase = IWC_IDLE;
      } else {
        wz->phase = IWC_ABORT;
      }
      return result;
    }
    if (done) {
      comp = CS_JUST_COMPLETED;
      wz->phase = IWC_IDLE;
    } else if (wz->phase == IWC_START) {
      wz->phase = IWC_CONTINUE;
    }
  }

  if (completed != NULL) {
    *completed = comp;
  }
  return UDS_SUCCESS;
}

/*****************************************************************************/
int performIndexComponentChapterWriterSave(IndexComponent *component)
{
  WriteZone *wz = NULL;
  int result = resolveWriteZone(component, 0, &wz);
  if (result != UDS_SUCCESS) {
    return result;
  }

  if ((wz->phase != IWC_IDLE) && (wz->phase != IWC_DONE)) {
    bool done = false;
    IncrementalWriter incrFunc = getIncrementalWriter(component);
    int result = ASSERT(incrFunc != NULL, "no writer function");
    if (result != UDS_SUCCESS) {
      return result;
    }
    result = (*incrFunc)(component, wz->writer, 0, wz->phase, &done);
    if (result != UDS_SUCCESS) {
      if (wz->phase == IWC_ABORT) {
        wz->phase = IWC_IDLE;
      } else {
        wz->phase = IWC_ABORT;
      }
      return result;
    }
    if (done) {
      wz->phase = IWC_DONE;
    } else if (wz->phase == IWC_START) {
      wz->phase = IWC_CONTINUE;
    }
  }
  return UDS_SUCCESS;
}

/*****************************************************************************/
int finishIndexComponentZoneSave(IndexComponent   *component,
                                 unsigned int      zone,
                                 CompletionStatus *completed)
{
  WriteZone *wz = NULL;
  int result = resolveWriteZone(component, zone, &wz);
  if (result != UDS_SUCCESS) {
    return result;
  }

  CompletionStatus comp;
  switch (wz->phase) {
    case IWC_IDLE:
      comp = CS_COMPLETED_PREVIOUSLY;
      break;

    case IWC_DONE:
      comp = CS_JUST_COMPLETED;
      break;

    default:
      comp = CS_NOT_COMPLETED;
  }

  IncrementalWriter incrFunc = getIncrementalWriter(component);
  if ((wz->phase >= IWC_START) && (wz->phase < IWC_ABORT)) {
    bool done = false;
    int result = (*incrFunc)(component, wz->writer, zone, IWC_FINISH, &done);
    if (result != UDS_SUCCESS) {
      wz->phase = IWC_ABORT;
      return result;
    }
    if (!done) {
      logWarning("finish incremental save did not complete for %s zone %u",
                 component->info->name, zone);
      return UDS_CHECKPOINT_INCOMPLETE;
    }
    wz->phase = IWC_IDLE;
    comp = CS_JUST_COMPLETED;
  }

  if (completed != NULL) {
    *completed = comp;
  }
  return UDS_SUCCESS;
}

/*****************************************************************************/
int finishIndexComponentIncrementalSave(IndexComponent *component)
{
  unsigned int zone;
  for (zone = 0; zone < component->numZones; ++zone) {
    WriteZone *wz = component->writeZones[zone];
    IncrementalWriter incrFunc = getIncrementalWriter(component);
    if ((wz->phase != IWC_IDLE) && (wz->phase != IWC_DONE)) {
      // Note: this is only safe if no other threads are currently processing
      // this particular index
      bool done = false;
      int result = (*incrFunc)(component, wz->writer, zone, IWC_FINISH, &done);
      if (result != UDS_SUCCESS) {
        return result;
      }
      if (!done) {
        logWarning("finishing incremental save did not complete for %s zone %u",
                   component->info->name, zone);
        return UDS_UNEXPECTED_RESULT;
      }
      wz->phase = IWC_IDLE;
    }

    if ((wz->writer != NULL) && !wasBufferedWriterUsed(wz->writer)) {
      return logErrorWithStringError(UDS_CHECKPOINT_INCOMPLETE,
                                     "component %s zone %u did not get written",
                                     component->info->name, zone);
    }

    int result = closeBufferedWriter(wz);
    if (result != UDS_SUCCESS) {
      return result;
    }
  }

  return UDS_SUCCESS;
}

/*****************************************************************************/
int abortIndexComponentZoneSave(IndexComponent   *component,
                                unsigned int      zone,
                                CompletionStatus *status)
{
  WriteZone *wz = NULL;
  int result = resolveWriteZone(component, zone, &wz);
  if (result != UDS_SUCCESS) {
    return result;
  }

  CompletionStatus comp = CS_COMPLETED_PREVIOUSLY;

  IncrementalWriter incrFunc = getIncrementalWriter(component);
  if ((wz->phase != IWC_IDLE) && (wz->phase != IWC_DONE)) {
    result = (*incrFunc)(component, wz->writer, zone, IWC_ABORT, NULL);
    wz->phase = IWC_IDLE;
    if (result != UDS_SUCCESS) {
      return result;
    }
    comp = CS_JUST_COMPLETED;
  }

  if (status != NULL) {
    *status = comp;
  }
  return UDS_SUCCESS;
}

/*****************************************************************************/
int abortIndexComponentIncrementalSave(IndexComponent *component)
{
  int result = UDS_SUCCESS;
  unsigned int zone;
  for (zone = 0; zone < component->numZones; ++zone) {
    WriteZone *wz = component->writeZones[zone];
    IncrementalWriter incrFunc = getIncrementalWriter(component);
    if ((wz->phase != IWC_IDLE) && (wz->phase != IWC_DONE)) {
      // Note: this is only safe if no other threads are currently processing
      // this particular index
      result = (*incrFunc)(component, wz->writer, zone, IWC_ABORT, NULL);
      wz->phase = IWC_IDLE;
      if (result != UDS_SUCCESS) {
        return result;
      }
    }

    int result = closeBufferedWriter(wz);
    if (result != UDS_SUCCESS) {
      return result;
    }
  }

  return UDS_SUCCESS;
}

/*****************************************************************************/
int discardIndexComponent(IndexComponent *component)
{
  if (!component->info->ioStorage) {
    return UDS_INVALID_ARGUMENT;
  }

  unsigned int numZones = 0;
  unsigned int saveSlot = 0;
  int result = findLatestIndexSaveSlot(component->state->layout, &numZones,
                                       &saveSlot);
  if (result != UDS_SUCCESS) {
    return result;
  }

  unsigned int oldSaveSlot = component->state->saveSlot;
  component->state->saveSlot = saveSlot;

  unsigned int z;
  for (z = 0; z < numZones; ++z) {
    BufferedWriter *writer;
    int result = openStateBufferedWriter(component->state,
                                         component->info->kind, z, &writer);
    if (result != UDS_SUCCESS) {
      break;
    }
    result = writeZerosToBufferedWriter(writer, UDS_BLOCK_SIZE);
    if (result != UDS_SUCCESS) {
      break;
    }
    result = flushBufferedWriter(writer);
    if (result != UDS_SUCCESS) {
      break;
    }
    freeBufferedWriter(writer);
  }

  component->state->saveSlot = oldSaveSlot;
  return result;
}
