/* Copyright (c) 2007-2018. The SimGrid Team. All rights reserved.          */

/* This program is free software; you can redistribute it and/or modify it
 * under the terms of the license (GNU LGPL) which comes with this package. */

#ifndef SIMGRID_MC_REGION_SNAPSHOT_HPP
#define SIMGRID_MC_REGION_SNAPSHOT_HPP

#include <cstddef>
#include <utility>

#include <memory>
#include <vector>

#include "xbt/base.h"

#include "src/mc/AddressSpace.hpp"
#include "src/mc/remote/RemotePtr.hpp"
#include "src/mc/sosp/ChunkedData.hpp"
#include "src/mc/sosp/PageStore.hpp"

namespace simgrid {
namespace mc {

enum class RegionType { Unknown = 0, Heap = 1, Data = 2 };

enum class StorageType { NoData = 0, Flat = 1, Chunked = 2, Privatized = 3 };

class Buffer {
private:
  enum class Type { Malloc, Mmap };
  void* data_ = nullptr;
  std::size_t size_;
  Type type_ = Type::Malloc;

  Buffer(std::size_t size, Type type = Type::Malloc);
  Buffer(void* data, std::size_t size, Type type = Type::Malloc) : data_(data), size_(size), type_(type) {}

public:
  Buffer() = default;
  void clear() noexcept;
  ~Buffer() noexcept { clear(); }

  static Buffer malloc(std::size_t size) { return Buffer(size, Type::Malloc); }
  static Buffer mmap(std::size_t size) { return Buffer(size, Type::Mmap); }

  // No copy
  Buffer(Buffer const& buffer) = delete;
  Buffer& operator=(Buffer const& buffer) = delete;

  // Move
  Buffer(Buffer&& that) noexcept : data_(that.data_), size_(that.size_), type_(that.type_)
  {
    that.data_ = nullptr;
    that.size_ = 0;
    that.type_ = Type::Malloc;
  }
  Buffer& operator=(Buffer&& that) noexcept
  {
    clear();
    data_      = that.data_;
    size_      = that.size_;
    type_      = that.type_;
    that.data_ = nullptr;
    that.size_ = 0;
    that.type_ = Type::Malloc;
    return *this;
  }

  void* get() { return data_; }
  const void* get() const { return data_; }
  std::size_t size() const { return size_; }
};

/** A copy/snapshot of a given memory region
 *
 *  Different types of region snapshot storage types exist:
 *
 *  * flat/dense snapshots are a simple copy of the region;
 *
 *  * sparse/per-page snapshots are snaapshots which shared
 *    identical pages.
 *
 *  * privatized (SMPI global variable privatization).
 *
 *  This is handled with a variant based approach:
 *
 *  * `storage_type` identified the type of storage;
 *
 *  * an anonymous enum is used to distinguish the relevant types for
 *    each type.
 */
class RegionSnapshot {
public:
  static const RegionType UnknownRegion = RegionType::Unknown;
  static const RegionType HeapRegion    = RegionType::Heap;
  static const RegionType DataRegion    = RegionType::Data;

private:
  RegionType region_type_;
  StorageType storage_type_;
  simgrid::mc::ObjectInformation* object_info_;

  /** @brief  Virtual address of the region in the simulated process */
  void* start_addr_;

  /** @brief Size of the data region in bytes */
  std::size_t size_;

  /** @brief Permanent virtual address of the region
   *
   * This is usually the same address as the simuilated process address.
   * However, when using SMPI privatization of global variables,
   * each SMPI process has its own set of global variables stored
   * at a different virtual address. The scheduler maps those region
   * on the region of the global variables.
   *
   * */
  void* permanent_addr_;

  Buffer flat_data_;
  ChunkedData page_numbers_;
  std::vector<RegionSnapshot> privatized_regions_;

public:
  RegionSnapshot()
      : region_type_(UnknownRegion)
      , storage_type_(StorageType::NoData)
      , object_info_(nullptr)
      , start_addr_(nullptr)
      , size_(0)
      , permanent_addr_(nullptr)
  {
  }
  RegionSnapshot(RegionType type, void* start_addr, void* permanent_addr, size_t size)
      : region_type_(type)
      , storage_type_(StorageType::NoData)
      , object_info_(nullptr)
      , start_addr_(start_addr)
      , size_(size)
      , permanent_addr_(permanent_addr)
  {
  }
  ~RegionSnapshot()                     = default;
  RegionSnapshot(RegionSnapshot const&) = default;
  RegionSnapshot& operator=(RegionSnapshot const&) = default;
  RegionSnapshot(RegionSnapshot&& that)
      : region_type_(that.region_type_)
      , storage_type_(that.storage_type_)
      , object_info_(that.object_info_)
      , start_addr_(that.start_addr_)
      , size_(that.size_)
      , permanent_addr_(that.permanent_addr_)
      , flat_data_(std::move(that.flat_data_))
      , page_numbers_(std::move(that.page_numbers_))
      , privatized_regions_(std::move(that.privatized_regions_))
  {
    that.clear();
  }
  RegionSnapshot& operator=(RegionSnapshot&& that)
  {
    region_type_        = that.region_type_;
    storage_type_       = that.storage_type_;
    object_info_        = that.object_info_;
    start_addr_         = that.start_addr_;
    size_               = that.size_;
    permanent_addr_     = that.permanent_addr_;
    flat_data_          = std::move(that.flat_data_);
    page_numbers_       = std::move(that.page_numbers_);
    privatized_regions_ = std::move(that.privatized_regions_);
    that.clear();
    return *this;
  }

  // Data

  void clear()
  {
    region_type_  = UnknownRegion;
    storage_type_ = StorageType::NoData;
    privatized_regions_.clear();
    page_numbers_.clear();
    flat_data_.clear();
    object_info_    = nullptr;
    start_addr_     = nullptr;
    size_           = 0;
    permanent_addr_ = nullptr;
  }

  void clear_data()
  {
    storage_type_ = StorageType::NoData;
    flat_data_.clear();
    page_numbers_.clear();
    privatized_regions_.clear();
  }

  void flat_data(Buffer data)
  {
    storage_type_ = StorageType::Flat;
    flat_data_    = std::move(data);
    page_numbers_.clear();
    privatized_regions_.clear();
  }
  const Buffer& flat_data() const { return flat_data_; }
  Buffer& flat_data() { return flat_data_; }

  void page_data(ChunkedData page_data)
  {
    storage_type_ = StorageType::Chunked;
    flat_data_.clear();
    page_numbers_ = std::move(page_data);
    privatized_regions_.clear();
  }
  ChunkedData const& page_data() const { return page_numbers_; }

  void privatized_data(std::vector<RegionSnapshot> data)
  {
    storage_type_ = StorageType::Privatized;
    flat_data_.clear();
    page_numbers_.clear();
    privatized_regions_ = std::move(data);
  }
  std::vector<RegionSnapshot> const& privatized_data() const { return privatized_regions_; }
  std::vector<RegionSnapshot>& privatized_data() { return privatized_regions_; }

  simgrid::mc::ObjectInformation* object_info() const { return object_info_; }
  void object_info(simgrid::mc::ObjectInformation* info) { object_info_ = info; }

  // Other getters

  RemotePtr<void> start() const { return remote(start_addr_); }
  RemotePtr<void> end() const { return remote((char*)start_addr_ + size_); }
  RemotePtr<void> permanent_address() const { return remote(permanent_addr_); }
  std::size_t size() const { return size_; }
  StorageType storage_type() const { return storage_type_; }
  RegionType region_type() const { return region_type_; }

  bool contain(RemotePtr<void> p) const { return p >= start() && p < end(); }
};

RegionSnapshot privatized_region(RegionType region_type, void* start_addr, void* permanent_addr, std::size_t size);
RegionSnapshot dense_region(RegionType type, void* start_addr, void* data_addr, std::size_t size);
simgrid::mc::RegionSnapshot sparse_region(RegionType type, void* start_addr, void* data_addr, std::size_t size);
simgrid::mc::RegionSnapshot region(RegionType type, void* start_addr, void* data_addr, std::size_t size);

} // namespace mc
} // namespace simgrid

typedef simgrid::mc::RegionSnapshot s_mc_mem_region_t;
typedef s_mc_mem_region_t* mc_mem_region_t;
#endif
