#include "ka_input.h"

#define RISCOS_FILE_HANDLING 1

#include <stdlib.h>

#include <string.h>
#include "config.h"
#include "ka_error.h"
#include "ka_log.h"
#include "ka_mem.h"
#include "ro_file.h"

#ifdef RISCOS_FILE_HANDLING
#include "swis.h"
#else
#include <stdio.h>
#endif

typedef struct
{
  ka_input_t hdr;
  uint32_t inputLen;           // size in bytes of current stream
#ifdef RISCOS_FILE_HANDLING
  uint32_t handle;             // RISC OS file handle
#else
  FILE*    handle;             // C lib file handle
#endif
  char     inputname[MAXPATH]; // full pathname of current stream
} data_t;

/**
 * Returns the length in bytes of the input stream.
 *
 * @param  f         Input stream handle.
 *
 * @returns Length in bytes of the input stream.
 */
static uint64_t ka_infile_getLen(ka_input_t* pInput)
{
  data_t* f = (data_t*) pInput;

  return (uint64_t) f->inputLen;
}

/**
 * Closes the input stream.
 *
 * @param  f         Input stream handle.
 *
 * @returns negative value in case of error.
 */
static int ka_infile_close(ka_input_t* pInput)
{
  data_t* f = (data_t*) pInput;

  if (f->handle)
  {
#ifdef RISCOS_FILE_HANDLING
    // Close file
    if (_swix(OS_Find, _INR(0,1), 0, f->handle))
      return -1;
#else
    fclose(f->handle);
#endif
    f->handle = 0;
  }

  return 0;
}

/**
 * Changes from position within the input stream.
 *
 * @param  f         Input stream handle.
 * @param  pos       New position.
 *
 * @returns negative value in case of error.
 */
static int ka_infile_seek(ka_input_t* pInput, uint64_t pos)
{
  data_t* f = (data_t*) pInput;

  // RISC OS does not support files larger than 4GB
  if (pos > 0xffffffff)
    pos = 0xffffffff;

#ifdef RISCOS_FILE_HANDLING
  uint32_t fpos = (int32_t) pos;

  // Set file pos
  if (_swix(OS_Args, _INR(0,2), 1, f->handle, fpos))
    return -1;

  return 0;
#else
  return fseek(f->handle, (long int) pos, SEEK_SET);
#endif
}

/**
 * Opens the input stream.
 *
 * @param  f         Input stream handle.
 *
 * @returns negative value in case of error.
 */
int ka_infile_open(ka_input_t* pInput, const char* inputname)
{
  data_t* f = (data_t*) pInput;
  snprintf(f->inputname, sizeof(f->inputname), "%s", inputname);

#ifdef RISCOS_FILE_HANDLING
  const _kernel_oserror* err = NULL;

  // Open file in read mode
  err = _swix(OS_Find, _INR(0,1)|_OUT(0), 0x4f, f->inputname, &f->handle);
  // Read file size
  if (!err) err = _swix(OS_Args, _INR(0,1)|_OUT(2), 2, f->handle, &f->inputLen);

  if (err)
  {
    ka_log(ka_log_error, "%s", err->errmess);
    ka_error_fill(f->hdr.pErrorBlock, "error2:%s", f->inputname);
    return -1;
  }
#else
  // Open file in read mode
  f->handle = fopen(f->inputname, "rb");

  if (!f->handle)
  {
    ka_error_fill(f->hdr.pErrorBlock, "error2:%s", f->inputname);
    return -1;
  }

  // Read file size
  fseek(f->handle, 0, SEEK_END);
  f->inputLen = (uint32_t) ftell(f->handle);
  fseek(f->handle, 0, SEEK_SET);
#endif

  return 0;
}

/**
 * Has enough data from current position in stream.
 *
 * @param  f         Input stream handle.
 * @param  nbr       Number of bytes expected.
 *
 * @returns 1 Ok, 0 less than nbr bytes remaining, -1 waiting.
 */
static int ka_infile_hasBytes(ka_input_t* pInput, int nbr)
{
  data_t* f = (data_t*) pInput;
  uint32_t fpos;

#ifdef RISCOS_FILE_HANDLING
  const _kernel_oserror* err = NULL;

  // Read current pos in file
  err = _swix(OS_Args, _INR(0,1)|_OUT(2), 0, f->handle, &fpos);
  if (err)
  {
    *f->hdr.pErrorBlock = *err;
    return 0;
  }
#else
  fpos = ftell(f->handle) + nbr;
#endif

  return (f->inputLen - fpos) >= nbr;
}

/**
 * Skip data from current position in stream.
 *
 * @param  f         Input stream handle.
 * @param  ptr       Destination pointer where to store the data.
 * @param  nbr       Number of bytes to skip.
 *
 * @return Number of bytes skipped.
 */
static int ka_infile_skip(ka_input_t* pInput, int nbr)
{
  data_t* f = (data_t*) pInput;
  uint32_t fpos;

#ifdef RISCOS_FILE_HANDLING
  const _kernel_oserror* err = NULL;

  // Read current pos in file
  err = _swix(OS_Args, _INR(0,1)|_OUT(2), 0, f->handle, &fpos);
  // Set file pos
  if (!err) err = _swix(OS_Args, _INR(0,2), 1, f->handle, fpos + nbr);
  if (err)
  {
    *f->hdr.pErrorBlock = *err;
    return -1;
  }

  return nbr;
#else
  fpos = ftell(f->handle) + nbr;
  fseek(f->handle, fpos, SEEK_SET);
  return nbr;
#endif
}

/**
 * Read data from current position in stream.
 *
 * @param  f         Input stream handle.
 * @param  ptr       Destination pointer where to store the data.
 * @param  nbr       Number of bytes to read.
 *
 * @return Number of bytes read.
 */
static int ka_infile_read(ka_input_t* pInput, void* ptr, int nbr)
{
  data_t* f = (data_t*) pInput;
  size_t toread;

#ifdef RISCOS_FILE_HANDLING
  const _kernel_oserror* err = NULL;

  // Read bytes in file
  err = _swix(OS_GBPB, _INR(0,3)|_OUT(3), 4, f->handle, ptr, nbr, &toread);
  if (err)
  {
    *f->hdr.pErrorBlock = *err;
    return -1;
  }

  return nbr - toread;
#else
  return fread(ptr, 1, nbr, f->handle);
#endif
}

/**
 * Returns the input stream position.
 *
 * @param  f         Input stream handle.
 *
 * @returns Position within the input stream.
 */
static uint64_t ka_infile_tell(ka_input_t* pInput)
{
  data_t* f = (data_t*) pInput;
  uint32_t fpos;

#ifdef RISCOS_FILE_HANDLING
  const _kernel_oserror* err = NULL;

  // Read current pos in file
  err = _swix(OS_Args, _INR(0,1)|_OUT(2), 0, f->handle, &fpos);

  if (err)
  {
    ka_log(ka_log_error, "%s", err->errmess);
    *f->hdr.pErrorBlock = *err;
    return 0;
  }

  return (uint64_t) fpos;
#else
  return (uint64_t) ftell(f->handle);
#endif
}

/**
 * Read data from current position in stream and add it to buffer.
 *
 * @param  f         Input stream handle.
 * @param  buffer    Buffer handle.
 * @param  pdone     EOF indecator to fill on exit.
 *
 * @returns Number of bytes read.
 */
static int ka_infile_buffer(ka_input_t* pInput, ka_buffer_t* pbuffer, int* pdone)
{
  uint8_t* pstart;
  uint8_t* pend;
  int len = ka_buffer_getFreeBlock(pbuffer, &pstart, &pend);
  int read = 0;

  *pdone = 0;

  if (len)
  {
    // Fill free block of data
    read = ka_infile_read(pInput, pstart, len);
    // Error ?
    if (read < 0)
      return -1;
    ka_buffer_setWritten(pbuffer, pstart + read);

    // Data was read, it will have to be pushed on stack
    // No more additional data will come
    if (read == 0)
      *pdone = 1;
  }

  return read;
}

/**
 * Releases the input stream and all its data.
 *
 * @param  f         Input stream handle to delete.
 */
static void ka_infile_delete(ka_input_t** ppInput)
{
  data_t* f = (data_t*) *ppInput;
  *ppInput = NULL;

  if (!f)
    return;

  ka_infile_close(&f->hdr);
  ka_mem_free(f);
}

static const kav_input_t kav_infile =
{ .name = "DVD"
, .FNdelete = ka_infile_delete
, .FNclose  = ka_infile_close
, .FNgetLen = ka_infile_getLen
, .FNtell   = ka_infile_tell
, .FNhasBytes = ka_infile_hasBytes
, .FNskip   = ka_infile_skip
, .FNseek   = ka_infile_seek
, .FNread   = ka_infile_read
, .FNbuffer = ka_infile_buffer
};

/**
 * Creates a new input stream handler.
 *
 * @param  inputname  Name of the input which is to serve as input.
 *
 * @returns Handle to new input stream.
 */
ka_input_t* ka_new_infile(ka_error_t* pErrorBlock)
{
  data_t* f = ka_mem_calloc(sizeof(*f));

  if (!f)
  {
    ka_error_fill(pErrorBlock, ka_error_nomem);
    return NULL;
  }
  f->hdr.vptr = &kav_infile;
  f->hdr.pErrorBlock = pErrorBlock;

  return &f->hdr;
}
