/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2013 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#ifndef XENIA_MEMORY_H_
#define XENIA_MEMORY_H_

#include <cstdint>
#include <memory>
#include <string>
#include <vector>

#include "xenia/base/memory.h"
#include "xenia/base/mutex.h"
#include "xenia/cpu/mmio_handler.h"

namespace xe {

enum SystemHeapFlag : uint32_t {
  kSystemHeapVirtual = 1 << 0,
  kSystemHeapPhysical = 1 << 1,

  kSystemHeapDefault = kSystemHeapVirtual,
};

enum MemoryAllocationFlag : uint32_t {
  kMemoryAllocationReserve = 1 << 0,
  kMemoryAllocationCommit = 1 << 1,
};

enum MemoryProtectFlag : uint32_t {
  kMemoryProtectRead = 1 << 0,
  kMemoryProtectWrite = 1 << 1,
  kMemoryProtectNoCache = 1 << 2,
  kMemoryProtectWriteCombine = 1 << 3,

  kMemoryProtectNoAccess = 0,
};

// Equivalent to the Win32 MEMORY_BASIC_INFORMATION struct.
struct HeapAllocationInfo {
  // A pointer to the base address of the region of pages.
  uint32_t base_address;
  // A pointer to the base address of a range of pages allocated by the
  // VirtualAlloc function. The page pointed to by the BaseAddress member is
  // contained within this allocation range.
  uint32_t allocation_base;
  // The memory protection option when the region was initially allocated.
  uint32_t allocation_protect;
  // The size of the region beginning at the base address in which all pages
  // have identical attributes, in bytes.
  uint32_t region_size;
  // The state of the pages in the region (commit/free/reserve).
  uint32_t state;
  // The access protection of the pages in the region.
  uint32_t protect;
  // The type of pages in the region (private).
  uint32_t type;
};

union PageEntry {
  struct {
    uint32_t base_address : 20;       // in 4k pages
    uint32_t region_page_count : 20;  // in 4k pages
    uint32_t allocation_protect : 4;
    uint32_t current_protect : 4;
    uint32_t state : 2;
    uint32_t reserved : 14;
  };
  uint64_t qword;
};

class BaseHeap {
 public:
  virtual ~BaseHeap();

  uint32_t page_size() const { return page_size_; }

  virtual void Dispose();

  void DumpMap();

  virtual bool Alloc(uint32_t size, uint32_t alignment,
                     uint32_t allocation_type, uint32_t protect, bool top_down,
                     uint32_t* out_address);
  virtual bool AllocFixed(uint32_t base_address, uint32_t size,
                          uint32_t alignment, uint32_t allocation_type,
                          uint32_t protect);
  virtual bool AllocRange(uint32_t low_address, uint32_t high_address,
                          uint32_t size, uint32_t alignment,
                          uint32_t allocation_type, uint32_t protect,
                          bool top_down, uint32_t* out_address);
  virtual bool Decommit(uint32_t address, uint32_t size);
  virtual bool Release(uint32_t address, uint32_t* out_region_size = nullptr);
  virtual bool Protect(uint32_t address, uint32_t size, uint32_t protect);

  bool QueryRegionInfo(uint32_t base_address, HeapAllocationInfo* out_info);
  bool QuerySize(uint32_t address, uint32_t* out_size);
  bool QueryProtect(uint32_t address, uint32_t* out_protect);
  uint32_t GetPhysicalAddress(uint32_t address);

 protected:
  BaseHeap();

  void Initialize(uint8_t* membase, uint32_t heap_base, uint32_t heap_size,
                  uint32_t page_size);

  uint8_t* membase_;
  uint32_t heap_base_;
  uint32_t heap_size_;
  uint32_t page_size_;
  xe::global_critical_region global_critical_region_;
  std::vector<PageEntry> page_table_;
};

class VirtualHeap : public BaseHeap {
 public:
  VirtualHeap();
  ~VirtualHeap() override;

  void Initialize(uint8_t* membase, uint32_t heap_base, uint32_t heap_size,
                  uint32_t page_size);
};

class PhysicalHeap : public BaseHeap {
 public:
  PhysicalHeap();
  ~PhysicalHeap() override;

  void Initialize(uint8_t* membase, uint32_t heap_base, uint32_t heap_size,
                  uint32_t page_size, VirtualHeap* parent_heap);

  bool Alloc(uint32_t size, uint32_t alignment, uint32_t allocation_type,
             uint32_t protect, bool top_down, uint32_t* out_address) override;
  bool AllocFixed(uint32_t base_address, uint32_t size, uint32_t alignment,
                  uint32_t allocation_type, uint32_t protect) override;
  bool AllocRange(uint32_t low_address, uint32_t high_address, uint32_t size,
                  uint32_t alignment, uint32_t allocation_type,
                  uint32_t protect, bool top_down,
                  uint32_t* out_address) override;
  bool Decommit(uint32_t address, uint32_t size) override;
  bool Release(uint32_t base_address,
               uint32_t* out_region_size = nullptr) override;
  bool Protect(uint32_t address, uint32_t size, uint32_t protect) override;

 protected:
  VirtualHeap* parent_heap_;
};

class Memory {
 public:
  Memory();
  ~Memory();

  int Initialize();

  const std::wstring& file_name() const { return file_name_; }

  inline uint8_t* virtual_membase() const { return virtual_membase_; }
  inline uint8_t* TranslateVirtual(uint32_t guest_address) const {
    return virtual_membase_ + guest_address;
  }
  template <typename T>
  inline T TranslateVirtual(uint32_t guest_address) const {
    return reinterpret_cast<T>(virtual_membase_ + guest_address);
  }

  inline uint8_t* physical_membase() const { return physical_membase_; }
  inline uint8_t* TranslatePhysical(uint32_t guest_address) const {
    return physical_membase_ + (guest_address & 0x1FFFFFFF);
  }
  template <typename T>
  inline T TranslatePhysical(uint32_t guest_address) const {
    return reinterpret_cast<T>(physical_membase_ +
                               (guest_address & 0x1FFFFFFF));
  }

  // TODO(benvanik): make poly memory utils for these.
  void Zero(uint32_t address, uint32_t size);
  void Fill(uint32_t address, uint32_t size, uint8_t value);
  void Copy(uint32_t dest, uint32_t src, uint32_t size);
  uint32_t SearchAligned(uint32_t start, uint32_t end, const uint32_t* values,
                         size_t value_count);

  bool AddVirtualMappedRange(uint32_t virtual_address, uint32_t mask,
                             uint32_t size, void* context,
                             cpu::MMIOReadCallback read_callback,
                             cpu::MMIOWriteCallback write_callback);
  cpu::MMIORange* LookupVirtualMappedRange(uint32_t virtual_address);

  uintptr_t AddPhysicalWriteWatch(uint32_t physical_address, uint32_t length,
                                  cpu::WriteWatchCallback callback,
                                  void* callback_context, void* callback_data);
  void CancelWriteWatch(uintptr_t watch_handle);

  uint32_t SystemHeapAlloc(uint32_t size, uint32_t alignment = 0x20,
                           uint32_t system_heap_flags = kSystemHeapDefault);
  void SystemHeapFree(uint32_t address);

  BaseHeap* LookupHeap(uint32_t address);
  BaseHeap* LookupHeapByType(bool physical, uint32_t page_size);

  void DumpMap();

 private:
  int MapViews(uint8_t* mapping_base);
  void UnmapViews();

 private:
  std::wstring file_name_;
  uint32_t system_page_size_ = 0;
  uint8_t* virtual_membase_ = nullptr;
  uint8_t* physical_membase_ = nullptr;

  xe::memory::FileMappingHandle mapping_ = nullptr;
  uint8_t* mapping_base_ = nullptr;
  union {
    struct {
      uint8_t* v00000000;
      uint8_t* v40000000;
      uint8_t* v7F000000;
      uint8_t* v80000000;
      uint8_t* v90000000;
      uint8_t* vA0000000;
      uint8_t* vC0000000;
      uint8_t* vE0000000;
      uint8_t* physical;
    };
    uint8_t* all_views[9];
  } views_ = {{0}};

  std::unique_ptr<cpu::MMIOHandler> mmio_handler_;

  struct {
    VirtualHeap v00000000;
    VirtualHeap v40000000;
    VirtualHeap v80000000;
    VirtualHeap v90000000;

    VirtualHeap physical;
    PhysicalHeap vA0000000;
    PhysicalHeap vC0000000;
    PhysicalHeap vE0000000;
  } heaps_;

  friend class BaseHeap;
};

}  // namespace xe

#endif  // XENIA_MEMORY_H_
