// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/fml/mapping.h"

#include <fcntl.h>
#include <io.h>
#include <windows.h>

#include <type_traits>

#include "flutter/fml/file.h"
#include "flutter/fml/platform/win/errors_win.h"
#include "flutter/fml/platform/win/wstring_conversion.h"

namespace fml {

Mapping::Mapping() = default;

Mapping::~Mapping() = default;

static bool IsWritable(
    std::initializer_list<FileMapping::Protection> protection_flags) {
  for (auto protection : protection_flags) {
    if (protection == FileMapping::Protection::kWrite) {
      return true;
    }
  }
  return false;
}

static bool IsExecutable(
    std::initializer_list<FileMapping::Protection> protection_flags) {
  for (auto protection : protection_flags) {
    if (protection == FileMapping::Protection::kExecute) {
      return true;
    }
  }
  return false;
}

FileMapping::FileMapping(const fml::UniqueFD& fd,
                         std::initializer_list<Protection> protections)
    : size_(0), mapping_(nullptr) {
  if (!fd.is_valid()) {
    return;
  }

  const auto mapping_size = ::GetFileSize(fd.get(), nullptr);

  if (mapping_size == INVALID_FILE_SIZE) {
    FML_DLOG(ERROR) << "Invalid file size. " << GetLastErrorMessage();
    return;
  }

  if (mapping_size == 0) {
    valid_ = true;
    return;
  }

  DWORD protect_flags = 0;
  bool read_only = !IsWritable(protections);

  if (IsExecutable(protections)) {
    protect_flags = PAGE_EXECUTE_READ;
  } else if (read_only) {
    protect_flags = PAGE_READONLY;
  } else {
    protect_flags = PAGE_READWRITE;
  }

  mapping_handle_.reset(::CreateFileMapping(fd.get(),       // hFile
                                            nullptr,        // lpAttributes
                                            protect_flags,  // flProtect
                                            0,              // dwMaximumSizeHigh
                                            0,              // dwMaximumSizeLow
                                            nullptr         // lpName
                                            ));

  if (!mapping_handle_.is_valid()) {
    return;
  }

  const DWORD desired_access = read_only ? FILE_MAP_READ : FILE_MAP_WRITE;

  auto mapping = reinterpret_cast<uint8_t*>(
      MapViewOfFile(mapping_handle_.get(), desired_access, 0, 0, mapping_size));

  if (mapping == nullptr) {
    FML_DLOG(ERROR) << "Could not setup file mapping. "
                    << GetLastErrorMessage();
    return;
  }

  mapping_ = mapping;
  size_ = mapping_size;
  valid_ = true;
  if (IsWritable(protections)) {
    mutable_mapping_ = mapping_;
  }
}

FileMapping::~FileMapping() {
  if (mapping_ != nullptr) {
    UnmapViewOfFile(mapping_);
  }
}

size_t FileMapping::GetSize() const {
  return size_;
}

const uint8_t* FileMapping::GetMapping() const {
  return mapping_;
}

bool FileMapping::IsValid() const {
  return valid_;
}

}  // namespace fml