/*
 * 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/bufferedReader.c#5 $
 */

#include "bufferedReader.h"

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

#ifndef __KERNEL__
/*
 * Define sector_t.  The kernel really wants us to use it.  The code becomes
 * ugly if we need to #ifdef every usage of sector_t.  Note that the of #define
 * means that even if a user mode include typedefs sector_t, it will not affect
 * this module.
 */
#define sector_t uint64_t
#endif

struct bufferedReader {
#ifdef __KERNEL__
  // IOFactory owning the block device
  IOFactory              *br_factory;
  // The dm_bufio_client to read from
  struct dm_bufio_client *br_client;
  // The current dm_buffer
  struct dm_buffer       *br_buffer;
  // The number of blocks that can be read from
  sector_t                br_limit;
  // Number of the current block
  sector_t                br_blockNumber;
#else
  // Region to read from
  IORegion               *br_region;
  // Number of the current block
  uint64_t                br_blockNumber;
#endif
  // Start of the buffer
  byte                   *br_start;
  // End of the data read from the buffer
  byte                   *br_pointer;
};

#ifdef __KERNEL__
/*****************************************************************************/
static void readAhead(BufferedReader *br, sector_t blockNumber)
{
  if (blockNumber < br->br_limit) {
    enum { MAX_READ_AHEAD = 4 };
    size_t readAhead = minSizeT(MAX_READ_AHEAD, br->br_limit - blockNumber);
    dm_bufio_prefetch(br->br_client, blockNumber, readAhead);
  }
}
#endif

/*****************************************************************************/
#ifdef __KERNEL__
int makeBufferedReader(IOFactory               *factory,
                       struct dm_bufio_client  *client,
                       sector_t                 blockLimit,
                       BufferedReader         **readerPtr)
{
  BufferedReader *reader = NULL;
  int result = ALLOCATE(1, BufferedReader, "buffered reader", &reader);
  if (result != UDS_SUCCESS) {
    return result;
  }

  *reader = (BufferedReader) {
    .br_factory     = factory,
    .br_client      = client,
    .br_buffer      = NULL,
    .br_limit       = blockLimit,
    .br_blockNumber = 0,
    .br_start       = NULL,
    .br_pointer     = NULL,
  };
  
  readAhead(reader,0);
  getIOFactory(factory);
  *readerPtr = reader;
  return UDS_SUCCESS;
}
#else
int makeBufferedReader(IORegion *region, BufferedReader **readerPtr)
{
  byte *data;
  int result = ALLOCATE_IO_ALIGNED(UDS_BLOCK_SIZE, byte,
                                   "buffer writer buffer", &data);
  if (result != UDS_SUCCESS) {
    return result;
  }

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

  *reader = (BufferedReader) {
    .br_region      = region,
    .br_blockNumber = 0,
    .br_start       = data,
    .br_pointer     = NULL,
  };
  
  getIORegion(region);
  *readerPtr = reader;
  return UDS_SUCCESS;
}
#endif

/*****************************************************************************/
void freeBufferedReader(BufferedReader *br)
{
  if (br == NULL) {
    return;
  }
#ifdef __KERNEL__
  if (br->br_buffer != NULL) {
    dm_bufio_release(br->br_buffer);
  }
  dm_bufio_client_destroy(br->br_client);
  putIOFactory(br->br_factory);
#else
  putIORegion(br->br_region);
  FREE(br->br_start);
#endif
  FREE(br);
}

/*****************************************************************************/
static int positionReader(BufferedReader *br,
                          sector_t        blockNumber,
                          off_t           offset)
{
  if ((br->br_pointer == NULL) || (blockNumber != br->br_blockNumber)) {
#ifdef __KERNEL__
    if (blockNumber >= br->br_limit) {
      return UDS_OUT_OF_RANGE;
    }
    if (br->br_buffer != NULL) {
      dm_bufio_release(br->br_buffer);
      br->br_buffer = NULL;
    }
    struct dm_buffer *buffer = NULL;
    void *data = dm_bufio_read(br->br_client, blockNumber, &buffer);
    if (IS_ERR(data)) {
      return -PTR_ERR(data);
    }
    br->br_buffer = buffer;
    br->br_start  = data;
    if (blockNumber == br->br_blockNumber + 1) {
      readAhead(br, blockNumber + 1);
    }
#else
    int result = readFromRegion(br->br_region, blockNumber * UDS_BLOCK_SIZE,
                                br->br_start, UDS_BLOCK_SIZE, NULL);
    if (result != UDS_SUCCESS) {
      logWarningWithStringError(result, "%s got readFromRegion error",
                                __func__);
      return result;
    }
#endif
  }
  br->br_blockNumber = blockNumber;
  br->br_pointer     = br->br_start + offset;
  return UDS_SUCCESS;
}

/*****************************************************************************/
static size_t bytesRemainingInReadBuffer(BufferedReader *br)
{
  return (br->br_pointer == NULL
          ? 0
          : br->br_start + UDS_BLOCK_SIZE - br->br_pointer);
}

/*****************************************************************************/
int readFromBufferedReader(BufferedReader *br, void *data, size_t length)
{
  byte *dp = data;
  int result = UDS_SUCCESS;
  while (length > 0) {
    if (bytesRemainingInReadBuffer(br) == 0) {
      sector_t blockNumber = br->br_blockNumber;
      if (br->br_pointer != NULL) {
        ++blockNumber;
      }
      result = positionReader(br, blockNumber, 0);
      if (result != UDS_SUCCESS) {
        break;
      }
    }

    size_t avail = bytesRemainingInReadBuffer(br);
    size_t chunk = minSizeT(length, avail);
    memcpy(dp, br->br_pointer, chunk);
    length         -= chunk;
    dp             += chunk;
    br->br_pointer += chunk;
  }

  if (((result == UDS_OUT_OF_RANGE) || (result == UDS_END_OF_FILE))
      && (dp - (byte *) data > 0)) {
    result = UDS_SHORT_READ;
  }
  return result;
}

/*****************************************************************************/
int verifyBufferedData(BufferedReader *br,
                       const void     *value,
                       size_t          length)
{
  const byte *vp = value;
  sector_t startingBlockNumber = br->br_blockNumber;
  int      startingOffset      = br->br_pointer - br->br_start;
  while (length > 0) {
    if (bytesRemainingInReadBuffer(br) == 0) {
      sector_t blockNumber = br->br_blockNumber;
      if (br->br_pointer != NULL) {
        ++blockNumber;
      }
      int result = positionReader(br, blockNumber, 0);
      if (result != UDS_SUCCESS) {
        positionReader(br, startingBlockNumber, startingOffset);
        return UDS_CORRUPT_FILE;
      }
    }

    size_t avail = bytesRemainingInReadBuffer(br);
    size_t chunk = minSizeT(length, avail);
    if (memcmp(vp, br->br_pointer, chunk) != 0) {
      positionReader(br, startingBlockNumber, startingOffset);
      return UDS_CORRUPT_FILE;
    }
    length         -= chunk;
    vp             += chunk;
    br->br_pointer += chunk;
  }

  return UDS_SUCCESS;
}
