#include <limits.h>
#include <stdio.h>

#ifdef _MSC_VER
#include <io.h>
#else  // _MSC_VER
#include <unistd.h>
#endif  // _MSC_VER

#include "reader.h"

namespace marisa {

Reader::Reader()
    : file_(NULL), fd_(-1), stream_(NULL), needs_fclose_(false) {}

Reader::Reader(std::FILE *file)
    : file_(file), fd_(-1), stream_(NULL), needs_fclose_(false) {}

Reader::Reader(int fd)
    : file_(NULL), fd_(fd), stream_(NULL), needs_fclose_(false) {}

Reader::Reader(std::istream *stream)
    : file_(NULL), fd_(-1), stream_(stream), needs_fclose_(false) {}

Reader::~Reader() {
  if (needs_fclose_) {
    ::fclose(file_);
  }
}

void Reader::open(const char *filename, long offset, int whence) {
  MARISA_THROW_IF(is_open(), MARISA_STATE_ERROR);
  MARISA_THROW_IF(filename == NULL, MARISA_PARAM_ERROR);
#ifdef _MSC_VER
  std::FILE *file = NULL;
  if (::fopen_s(&file, filename, "rb") != 0) {
    MARISA_THROW(MARISA_IO_ERROR);
  }
#else  // _MSC_VER
  std::FILE * const file = ::fopen(filename, "rb");
  MARISA_THROW_IF(file == NULL, MARISA_IO_ERROR);
#endif  // _MSC_VER
  if (::fseek(file, offset, whence) != 0) {
    ::fclose(file);
    MARISA_THROW(MARISA_IO_ERROR);
  }
  file_ = file;
  needs_fclose_ = true;
}

void Reader::clear() {
  Reader().swap(this);
}

void Reader::swap(Reader *rhs) {
  MARISA_THROW_IF(rhs == NULL, MARISA_PARAM_ERROR);
  Swap(&file_, &rhs->file_);
  Swap(&fd_, &rhs->fd_);
  Swap(&stream_, &rhs->stream_);
  Swap(&needs_fclose_, &rhs->needs_fclose_);
}

void Reader::read_data(void *buf, std::size_t size) {
  if (fd_ != -1) {
    while (size != 0) {
#ifdef _MSC_VER
      const unsigned int count = (size < INT_MAX) ? size : INT_MAX;
      const int size_read = _read(fd_, buf, count);
#else  // _MSC_VER
      const ::size_t count = (size < SSIZE_MAX) ? size : SSIZE_MAX;
      const ::ssize_t size_read = ::read(fd_, buf, count);
#endif  // _MSC_VER
      MARISA_THROW_IF(size_read <= 0, MARISA_IO_ERROR);
      buf = static_cast<char *>(buf) + size_read;
      size -= size_read;
    }
  } else if (file_ != NULL) {
    if (::fread(buf, 1, size, file_) != size) {
      MARISA_THROW(MARISA_IO_ERROR);
    }
  } else if (stream_ != NULL) {
    if (!stream_->read(static_cast<char *>(buf), size)) {
      MARISA_THROW(MARISA_IO_ERROR);
    }
  } else {
    MARISA_THROW(MARISA_STATE_ERROR);
  }
}

}  // namespace marisa