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

#include "bufferedWriter.h"

#include "compiler.h"
#include "errors.h"
#include "ioFactory.h"
#include "logger.h"
#include "memoryAlloc.h"
#include "numeric.h"


struct bufferedWriter {
#ifdef __KERNEL__
  // IOFactory owning the block device
  IOFactory              *bw_factory;
  // The dm_bufio_client to write to
  struct dm_bufio_client *bw_client;
  // The current dm_buffer
  struct dm_buffer       *bw_buffer;
  // The number of blocks that can be written to
  sector_t                bw_limit;
  // Number of the current block
  sector_t                bw_blockNumber;
#else
  // Region to write to
  IORegion               *bw_region;
  // Number of the current block
  uint64_t                bw_blockNumber;
#endif
  // Start of the buffer
  byte                   *bw_start;
  // End of the data written to the buffer
  byte                   *bw_pointer;
  // Error code
  int                     bw_error;
  // Have writes been done?
  bool                    bw_used;
};

#ifdef __KERNEL__
/*****************************************************************************/
__attribute__((warn_unused_result))
int prepareNextBuffer(BufferedWriter *bw)
{
  if (bw->bw_blockNumber >= bw->bw_limit) {
    bw->bw_error = UDS_OUT_OF_RANGE;
    return UDS_OUT_OF_RANGE;
  }

  struct dm_buffer *buffer = NULL;
  void *data = dm_bufio_new(bw->bw_client, bw->bw_blockNumber, &buffer);
  if (IS_ERR(data)) {
    bw->bw_error = -PTR_ERR(data);
    return bw->bw_error;
  }
  bw->bw_buffer  = buffer;
  bw->bw_start   = data;
  bw->bw_pointer = data;
  return UDS_SUCCESS;
}

/*****************************************************************************/
int flushPreviousBuffer(BufferedWriter *bw)
{
  if (bw->bw_buffer != NULL) {
    if (bw->bw_error == UDS_SUCCESS) {
      size_t avail = spaceRemainingInWriteBuffer(bw);
      if (avail > 0) {
        memset(bw->bw_pointer, 0, avail);
      }
      dm_bufio_mark_buffer_dirty(bw->bw_buffer);
    }
    dm_bufio_release(bw->bw_buffer);
    bw->bw_buffer  = NULL;
    bw->bw_start   = NULL;
    bw->bw_pointer = NULL;
    bw->bw_blockNumber++;
  }
  return bw->bw_error;
}
#endif

/*****************************************************************************/
#ifdef __KERNEL__
int makeBufferedWriter(IOFactory               *factory,
                       struct dm_bufio_client  *client,
                       sector_t                 blockLimit,
                       BufferedWriter         **writerPtr)
{
  BufferedWriter *writer;
  int result = ALLOCATE(1, BufferedWriter, "buffered writer", &writer);
  if (result != UDS_SUCCESS) {
    return result;
  }

  *writer = (BufferedWriter) {
    .bw_factory     = factory,
    .bw_client      = client,
    .bw_buffer      = NULL,
    .bw_limit       = blockLimit,
    .bw_start       = NULL,
    .bw_pointer     = NULL,
    .bw_blockNumber = 0,
    .bw_error       = UDS_SUCCESS,
    .bw_used        = false,
  };

  getIOFactory(factory);
  *writerPtr = writer;
  return UDS_SUCCESS;
}
#else
int makeBufferedWriter(IORegion *region, BufferedWriter **writerPtr)
{
  byte *data;
  int result = ALLOCATE_IO_ALIGNED(UDS_BLOCK_SIZE, byte,
                                   "buffer writer buffer", &data);
  if (result != UDS_SUCCESS) {
    return result;
  }

  BufferedWriter *writer;
  result = ALLOCATE(1, BufferedWriter, "buffered writer", &writer);
  if (result != UDS_SUCCESS) {
    FREE(data);
    return result;
  }

  *writer = (BufferedWriter) {
    .bw_region      = region,
    .bw_start       = data,
    .bw_pointer     = data,
    .bw_blockNumber = 0,
    .bw_error       = UDS_SUCCESS,
    .bw_used        = false,
  };

  getIORegion(region);
  *writerPtr = writer;
  return UDS_SUCCESS;
}
#endif

/*****************************************************************************/
void freeBufferedWriter(BufferedWriter *bw)
{
  if (bw == NULL) {
    return;
  }
#ifdef __KERNEL__
  flushPreviousBuffer(bw);
  int result = -dm_bufio_write_dirty_buffers(bw->bw_client);
#else
  int result = syncRegionContents(bw->bw_region);
#endif
  if (result != UDS_SUCCESS) {
    logWarningWithStringError(result, "%s cannot sync storage", __func__);
  }
#ifdef __KERNEL__
  dm_bufio_client_destroy(bw->bw_client);
  putIOFactory(bw->bw_factory);
#else
  putIORegion(bw->bw_region);
  FREE(bw->bw_start);
#endif
  FREE(bw);
}

/*****************************************************************************/
static INLINE size_t spaceUsedInBuffer(BufferedWriter *bw)
{
  return bw->bw_pointer - bw->bw_start;
}

/*****************************************************************************/
size_t spaceRemainingInWriteBuffer(BufferedWriter *bw)
{
  return UDS_BLOCK_SIZE - spaceUsedInBuffer(bw);
}

/*****************************************************************************/
int writeToBufferedWriter(BufferedWriter *bw, const void *data, size_t len)
{
  if (bw->bw_error != UDS_SUCCESS) {
    return bw->bw_error;
  }

  const byte *dp = data;
  int result = UDS_SUCCESS;
  while ((len > 0) && (result == UDS_SUCCESS)) {
#ifdef __KERNEL__
    if (bw->bw_buffer == NULL) {
      result = prepareNextBuffer(bw);
      continue;
    }
#endif

    size_t avail = spaceRemainingInWriteBuffer(bw);
    size_t chunk = minSizeT(len, avail);
    memcpy(bw->bw_pointer, dp, chunk);
    len            -= chunk;
    dp             += chunk;
    bw->bw_pointer += chunk;

    if (spaceRemainingInWriteBuffer(bw) == 0) {
      result = flushBufferedWriter(bw);
    }
  }

  bw->bw_used = true;
  return result;
}

/*****************************************************************************/
int writeZerosToBufferedWriter(BufferedWriter *bw, size_t len)
{
  if (bw->bw_error != UDS_SUCCESS) {
    return bw->bw_error;
  }

  int result = UDS_SUCCESS;
  while ((len > 0) && (result == UDS_SUCCESS)) {
#ifdef __KERNEL__
    if (bw->bw_buffer == NULL) {
      result = prepareNextBuffer(bw);
      continue;
    }
#endif

    size_t avail = spaceRemainingInWriteBuffer(bw);
    size_t chunk = minSizeT(len, avail);
    memset(bw->bw_pointer, 0, chunk);
    len            -= chunk;
    bw->bw_pointer += chunk;

    if (spaceRemainingInWriteBuffer(bw) == 0) {
      result = flushBufferedWriter(bw);
    }
  }

  bw->bw_used = true;
  return result;
}

/*****************************************************************************/
int flushBufferedWriter(BufferedWriter *bw)
{
  if (bw->bw_error != UDS_SUCCESS) {
    return bw->bw_error;
  }

#ifdef __KERNEL__
  return flushPreviousBuffer(bw);
#else
  size_t n = spaceUsedInBuffer(bw);
  if (n > 0) {
    int result = writeToRegion(bw->bw_region,
                               bw->bw_blockNumber * UDS_BLOCK_SIZE,
                               bw->bw_start, UDS_BLOCK_SIZE, n);
    if (result != UDS_SUCCESS) {
      return bw->bw_error = result;
    } else {
      bw->bw_pointer = bw->bw_start;
      bw->bw_blockNumber++;
    }
  }
  return UDS_SUCCESS;
#endif
}

/*****************************************************************************/
bool wasBufferedWriterUsed(const BufferedWriter *bw)
{
  return bw->bw_used;
}

/*****************************************************************************/
void noteBufferedWriterUsed(BufferedWriter *bw)
{
  bw->bw_used = true;
}
