// Copyright (c) the JPEG XL Project Authors.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#include "mmap.h"

#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <memory>
#include <utility>

#include "lib/base/common.h"
#include "lib/base/status.h"

#if defined(__unix__) || defined(__unix) || \
    defined(__APPLE__) && defined(__MACH__)
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

namespace jxl {

struct MemoryMappedFileImpl {
  static StatusOr<std::unique_ptr<MemoryMappedFileImpl>> Init(
      const char* path) {
    auto f = make_unique<MemoryMappedFileImpl>();
    f->fd = open(path, O_RDONLY);
    if (f->fd == -1) {
      return JXL_FAILURE("Cannot open file %s", path);
    }
    f->mmap_len = lseek(f->fd, 0, SEEK_END);
    lseek(f->fd, 0, SEEK_SET);

    f->ptr = mmap(nullptr, f->mmap_len, PROT_READ, MAP_SHARED, f->fd, 0);
    if (f->ptr == MAP_FAILED) {
      return JXL_FAILURE("mmap failure");
    }
    return f;
  }

  const uint8_t* data() const { return reinterpret_cast<const uint8_t*>(ptr); }
  size_t size() const { return mmap_len; }

  ~MemoryMappedFileImpl() {
    if (fd != -1) {
      close(fd);
    }
    if (ptr != nullptr) {
      munmap(ptr, mmap_len);
    }
  }

  int fd = -1;
  size_t mmap_len = 0;
  void* ptr = nullptr;
};

}  // namespace jxl

#elif defined(_WIN32)
#include <string.h>
#include <windows.h>

namespace {

struct HandleDeleter {
  void operator()(const HANDLE handle) const {
    if (handle != INVALID_HANDLE_VALUE) {
      CloseHandle(handle);
    }
  }
};
using HandleUniquePtr =
    std::unique_ptr<std::remove_pointer<HANDLE>::type, HandleDeleter>;

}  // namespace

namespace jxl {

struct MemoryMappedFileImpl {
  static StatusOr<std::unique_ptr<MemoryMappedFileImpl>> Init(
      const char* path) {
    auto f = make_unique<MemoryMappedFileImpl>();
    std::wstring stemp = std::wstring(path, path + strlen(path));
    f->handle.reset(CreateFileW(stemp.c_str(), GENERIC_READ, FILE_SHARE_READ,
                                nullptr, OPEN_EXISTING,
                                FILE_FLAG_SEQUENTIAL_SCAN, nullptr));
    if (f->handle.get() == INVALID_HANDLE_VALUE) {
      return JXL_FAILURE("Cannot open file %s", path);
    }
    if (!GetFileSizeEx(f->handle.get(), &f->fsize)) {
      return JXL_FAILURE("Cannot get file size (%s)", path);
    }
    f->handle_mapping.reset(CreateFileMappingW(f->handle.get(), nullptr,
                                               PAGE_READONLY, 0, 0, nullptr));
    if (f->handle_mapping == nullptr) {
      return JXL_FAILURE("Cannot create memory mapping (%s)", path);
    }
    f->ptr = MapViewOfFile(f->handle_mapping.get(), FILE_MAP_READ, 0, 0, 0);
    return f;
  }

  ~MemoryMappedFileImpl() { UnmapViewOfFile(ptr); }

  const uint8_t* data() const { return reinterpret_cast<const uint8_t*>(ptr); }
  size_t size() const { return fsize.QuadPart; }

  HandleUniquePtr handle;
  HandleUniquePtr handle_mapping;
  LARGE_INTEGER fsize;
  void* ptr = nullptr;
};

}  // namespace jxl

#else

namespace jxl {

struct MemoryMappedFileImpl {
  static StatusOr<std::unique_ptr<MemoryMappedFileImpl>> Init(
      const char* path) {
    return JXL_FAILURE("Memory mapping not supported on this system");
  }

  const uint8_t* data() const { return nullptr; }
  size_t size() const { return 0; }
};

}  // namespace jxl

#endif

namespace jxl {

StatusOr<MemoryMappedFile> MemoryMappedFile::Init(const char* path) {
  JXL_ASSIGN_OR_RETURN(auto mmf, MemoryMappedFileImpl::Init(path));
  MemoryMappedFile ret;
  ret.impl_ = std::move(mmf);
  return ret;
}

MemoryMappedFile::MemoryMappedFile() = default;
MemoryMappedFile::~MemoryMappedFile() = default;
MemoryMappedFile::MemoryMappedFile(MemoryMappedFile&&) noexcept = default;
MemoryMappedFile& MemoryMappedFile::operator=(MemoryMappedFile&&) noexcept =
    default;

const uint8_t* MemoryMappedFile::data() const { return impl_->data(); }
size_t MemoryMappedFile::size() const { return impl_->size(); }
}  // namespace jxl
