/*------------------------------------------------------------------------------
 src/lib/dj/fileIo.cxx
 
 Copyright (c) 2004-2006 Darby Johnston.
 All rights reserved.
 Use is subject to license terms.
------------------------------------------------------------------------------*/

//#define DJ_DBG
//#undef DJ_MMAP
#include "djFileIo.h"
#include <sys/stat.h>
#ifdef DJ_MMAP
#include <sys/mman.h>
#endif
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

namespace dj {

FileIo::FileIo() :
  _f(-1),
# ifdef DJ_MMAP
  _mmap((void *)-1), _mmapP(0), _mmapEnd(0), _mmapSize(0),
# endif
  _mode(MODE(0)), _endian(false)
{}

FileIo::~FileIo() { close(); }

bool FileIo::open(const Str & in, MODE mode)
{
  DJ_DBG_FNC("dj::FileIo::open");
  DJ_DBG_PRN("in = " << in);
  DJ_DBG_PRN("mode = " << mode);
  
  close();
  
  bool out = false;
do {

  _f = ::open(
    in.toAscii(),
    (WRITE == mode) ? (O_CREAT | O_WRONLY | O_TRUNC) : O_RDONLY,
    (WRITE == mode) ? (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) : 0
  );
  if (_f < 0) {
    DJ_DBG_PRN("open error");
    break;
  }

  _mode = mode;
  
# ifdef DJ_MMAP
  if (READ == _mode) {
    struct stat statBuf;
    if (fstat(_f, &statBuf) < 0) {
      DJ_DBG_PRN("stat error");
      break;
    }
    _mmapSize = statBuf.st_size;
    if (
      ((_mmap = ::mmap(0, _mmapSize, PROT_READ, MAP_SHARED, _f, 0)) ==
        (void *)-1)
    ) {
      DJ_DBG_PRN("mmap error");
      break;
    }
    _mmapP = reinterpret_cast<const uint8_t *>(_mmap);
    _mmapEnd = _mmapP + _mmapSize;
  }
# endif
  
  out = true;
} while (0);
  if (! out) close();
  return out;
}

void FileIo::close()
{
  DJ_DBG_FNC("dj::FileIo::close");
  
# ifdef DJ_MMAP
  if (_mmap != (void *)-1) {

    // XXX Solaris wants (char *)?
    //
    ::munmap((char *)_mmap, _mmapSize);
    _mmap = (void *)-1;
    _mmapP = 0;
    _mmapEnd = 0;
    _mmapSize = 0;
  }
# endif
  
  if (_f >= 0) {
    ::close(_f);
    _f = -1;
  }
}

bool FileIo::set8(const int8_t * in, int size)
  { return set(in, size, 1); }
bool FileIo::setU8(const uint8_t * in, int size)
  { return set(in, size, 1); }
bool FileIo::set16(const int16_t * in, int size)
  { return set(in, size, 2); }
bool FileIo::setU16(const uint16_t * in, int size)
  { return set(in, size, 2); }
bool FileIo::set32(const int32_t * in, int size)
  { return set(in, size, 4); }
bool FileIo::setU32(const uint32_t * in, int size)
  { return set(in, size, 4); }
bool FileIo::setF32(const float * in, int size)
  { return set(in, size, sizeof(float)); }

bool FileIo::set(const void * in, int size, int wordSize)
{
  if (! size || ! wordSize) return false;
  
  uint8_t * value = (uint8_t *)in;
  const bool endian = _endian && wordSize > 1;
  if (endian) {
    value = new uint8_t [size * wordSize];
    dj::endian(in, value, size, wordSize);
  }
  bool out = write(_f, value, size * wordSize) != -1;
  if (endian) delete [] value;

  return out;
}

bool FileIo::get8(int8_t * in, int size) { return get(in, size, 1); }
bool FileIo::getU8(uint8_t * in, int size) { return get(in, size, 1); }
bool FileIo::get16(int16_t * in, int size) { return get(in, size, 2); }
bool FileIo::getU16(uint16_t * in, int size) { return get(in, size, 2); }
bool FileIo::get32(int32_t * in, int size) { return get(in, size, 4); }
bool FileIo::getU32(uint32_t * in, int size) { return get(in, size, 4); }
bool FileIo::getF32(float * in, int size) { return get(in, size, 4); }

bool FileIo::get(void * in, int size, int wordSize)
{
# ifdef DJ_MMAP
  const uint8_t * p = _mmapP + size * wordSize;
  if (p > _mmapEnd) return false;
# endif
  
  //DJ_DBG_FNC("dj::FileIo::get");
  //DJ_DBG_PRN("size = " << size);
  //DJ_DBG_PRN("word size = " << wordSize);
  //DJ_DBG_PRN("position = " << _position);
  
# ifdef DJ_MMAP
  ::memcpy(in, _mmapP, size * wordSize);
  _mmapP = p;
# else
  if (::read(_f, in, size * wordSize) == -1) return false;
# endif

  if (_endian && wordSize > 1) {
    //DJ_DBG_PRN("endian convert");
    dj::endian(in, size, wordSize);
  }

  return true;
}

bool FileIo::seek(uint64_t offset, SEEK seek)
{
  //DJ_DBG_FNC("dj::FileIo::seek");
  //DJ_DBG_PRN("offset = " << offset);
  //DJ_DBG_PRN("seek = " << seek);
  
  switch (_mode) {

    case READ:
#     ifdef DJ_MMAP
      switch (seek) {
        case SET:
          _mmapP = reinterpret_cast<const uint8_t *>(_mmap) + offset;
          break;
        case CURRENT:
          _mmapP += offset;
          break;
      }
      if (_mmapP > _mmapEnd) return false;
#     else
      if (-1 == ::lseek(_f, offset, (CURRENT == seek) ? SEEK_CUR : SEEK_SET))
        return false;
#     endif
      break;

    case WRITE:
      if (-1 == ::lseek(_f, offset, (CURRENT == seek) ? SEEK_CUR : SEEK_SET))
        return false;
      break;
  }

  return true;
}

uint64_t FileIo::position() const
{
  switch (_mode) {
  
    case READ:

#     ifdef DJ_MMAP
      return _mmapP - reinterpret_cast<const uint8_t *>(_mmap);
#     endif

    case WRITE:
    
       // XXX Tell?
      //
      return lseek(_f, 0, SEEK_CUR);
  }
  
  return 0;
}

void FileIo::endian(bool in) { _endian = in; }

}
