#include <frostbite2D/resource/binary_file_stream_reader.h>

#include <SDL.h>

#include <algorithm>
#include <filesystem>

#include <frostbite2D/resource/asset.h>

namespace frostbite2D {

namespace {

namespace fs = std::filesystem;

fs::path toFsPath(const std::string& path) {
#ifdef _WIN32
  return fs::u8path(path);
#else
  return fs::path(path);
#endif
}

} // namespace

BinaryFileStreamReader::BinaryFileStreamReader(const std::string& filePath) {
  open(filePath);
}

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

bool BinaryFileStreamReader::open(const std::string& filePath) {
  close();

  Asset& asset = Asset::get();
  filePath_ = asset.resolvePath(filePath);
  stream_.open(toFsPath(filePath_), std::ios::binary);
  if (!stream_.is_open()) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                 "BinaryFileStreamReader: unable to open file: %s",
                 filePath.c_str());
    filePath_.clear();
    return false;
  }

  stream_.seekg(0, std::ios::end);
  std::streamoff endPos = stream_.tellg();
  if (endPos < 0) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                 "BinaryFileStreamReader: failed to determine file size: %s",
                 filePath_.c_str());
    close();
    return false;
  }

  size_ = static_cast<size_t>(endPos);
  stream_.seekg(0, std::ios::beg);
  position_ = 0;
  lastReadCount_ = 0;
  return true;
}

void BinaryFileStreamReader::close() {
  if (stream_.is_open()) {
    stream_.close();
  }
  filePath_.clear();
  size_ = 0;
  position_ = 0;
  lastReadCount_ = 0;
}

bool BinaryFileStreamReader::isOpen() const {
  return stream_.is_open();
}

size_t BinaryFileStreamReader::tell() const {
  return position_;
}

void BinaryFileStreamReader::seek(size_t pos) {
  if (!isOpen()) {
    position_ = 0;
    lastReadCount_ = 0;
    return;
  }

  position_ = std::min(pos, size_);
  lastReadCount_ = 0;
  stream_.clear();
  stream_.seekg(static_cast<std::streamoff>(position_), std::ios::beg);
}

void BinaryFileStreamReader::skip(size_t count) {
  seek(position_ + count);
}

bool BinaryFileStreamReader::eof() const {
  return position_ >= size_;
}

size_t BinaryFileStreamReader::size() const {
  return size_;
}

size_t BinaryFileStreamReader::remaining() const {
  return position_ >= size_ ? 0 : size_ - position_;
}

size_t BinaryFileStreamReader::lastReadCount() const {
  return lastReadCount_;
}

size_t BinaryFileStreamReader::read(char* buffer, size_t size) {
  if (!buffer || size == 0 || eof() || !isOpen()) {
    lastReadCount_ = 0;
    return 0;
  }

  size_t bytesToRead = std::min(size, remaining());
  stream_.clear();
  stream_.read(buffer, static_cast<std::streamsize>(bytesToRead));
  size_t bytesRead = static_cast<size_t>(stream_.gcount());
  position_ += bytesRead;
  lastReadCount_ = bytesRead;

  if (bytesRead != bytesToRead) {
    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
                 "BinaryFileStreamReader: incomplete read, expected %zu bytes, got %zu bytes from %s",
                 bytesToRead, bytesRead, filePath_.c_str());
  }

  return bytesRead;
}

std::vector<uint8> BinaryFileStreamReader::readBytes(size_t size) {
  std::vector<uint8> result;
  size_t bytesToRead = std::min(size, remaining());
  if (bytesToRead == 0) {
    lastReadCount_ = 0;
    return result;
  }

  result.resize(bytesToRead);
  size_t bytesRead = read(reinterpret_cast<char*>(result.data()), bytesToRead);
  result.resize(bytesRead);
  return result;
}

int8 BinaryFileStreamReader::readInt8() {
  return read<int8>();
}

int16 BinaryFileStreamReader::readInt16() {
  return read<int16>();
}

int32 BinaryFileStreamReader::readInt32() {
  return read<int32>();
}

int64 BinaryFileStreamReader::readInt64() {
  return read<int64>();
}

uint8 BinaryFileStreamReader::readUInt8() {
  return read<uint8>();
}

uint16 BinaryFileStreamReader::readUInt16() {
  return read<uint16>();
}

uint32 BinaryFileStreamReader::readUInt32() {
  return read<uint32>();
}

uint64 BinaryFileStreamReader::readUInt64() {
  return read<uint64>();
}

float BinaryFileStreamReader::readFloat() {
  return read<float>();
}

double BinaryFileStreamReader::readDouble() {
  return read<double>();
}

std::string BinaryFileStreamReader::readString(size_t length) {
  if (length == 0 || eof() || !isOpen()) {
    lastReadCount_ = 0;
    return {};
  }

  size_t bytesToRead = std::min(length, remaining());
  std::string result(bytesToRead, '\0');
  size_t bytesRead = read(result.data(), bytesToRead);
  result.resize(bytesRead);
  return result;
}

std::string BinaryFileStreamReader::readNullTerminatedString(size_t maxLength) {
  if (maxLength == 0 || eof() || !isOpen()) {
    lastReadCount_ = 0;
    return {};
  }

  std::string result;
  result.reserve(std::min(maxLength, remaining()));
  size_t bytesRead = 0;
  char ch = '\0';

  while (bytesRead < maxLength && position_ < size_) {
    stream_.clear();
    if (!stream_.get(ch)) {
      break;
    }

    ++position_;
    ++bytesRead;
    if (ch == '\0') {
      lastReadCount_ = bytesRead;
      return result;
    }

    result.push_back(ch);
  }

  lastReadCount_ = bytesRead;
  return result;
}

} // namespace frostbite2D
