#ifndef CMem_H
#define CMem_H

#include "GdCPP_Exports.h"
#include <stdint.h>
#include <mutex>
#include <map>

/**
  * @addtogroup Memory
 ** @brief
 ** @details
  * @{
  *
  */
///
/// \brief 内存管理的基类，实现：<br>
///
/// 需要了解的基本概念：
/// - 物理内存：内存条 + 内存交换文件
/// - 虚拟内存：应用程序认为它拥有连续的可用的内存，win10 64位有128T
/// - 内存交换文件paging file: 内存不够用了，系统将最近没使用的内存保存到文件中，用到再交换回来。
/// - page size：系统管理内存的单元，x86、x64系统使用4-KB, IA-64用8-KB.
/// - 内存分配粒度：windows从虚拟内存分配一个range时的最小单位，win10是64k
/// - large page: 大内存的应用为优化内存访问以比page大的多的单位使用物理内存（好像是2M）.
///   刚开机的时候分配没问题，电脑运行一段时间就没有足够连续的物理内存来分配了。
///
/// 需要了解的windows内存操作：reserve/commit/decommit/release/lock/unlock
///
/// 参考资料：
/// - 《windows核心编程》第五版
/// - [windows memory management](https://docs.microsoft.com/en-us/windows/win32/memory/memory-management)<br>
/// - [windows memory api](https://docs.microsoft.com/en-us/windows/win32/api/memoryapi)<br>
/// </pre>


/// \brief CMem 定义了一块内存，包括逻辑、物理两个方面。
/// - 内存的逻辑属性：包括内存地址、可用内存大小、外部容器大小。
/// - 内存的物理方法：包括内存如何分配、释放、调整大小的函数接口。
/// CMem作为所有内存的基类，物理层定义了一个在现有内存中取一部分使用。
class GDCPP_API CMem
{
public:
    virtual ~CMem()
    {
        //dealloc(); // 基类析构没必要清空属性变量了
    };

	// ----------- 逻辑内存 -----------
	
    /// \brief 内存起始地址。可以引用，但不要直接更改，通过alloc()、dealloc()来修改
    uint8_t* Addr = 0;

    /// \brief 可用内存大小
    size_t      memSize = 0;

    /// 容器大小，containerSize>=memSize
    /// 不同子类有不同的容器。
    /// - 基类容器就是已经分配好的一块内存
    /// - CWinMem的容器是AllocVirtual函数分配的虚拟内存空间
    size_t  containerSize = 0;

	bool selfAlloc = false; // 是否自己分配的内存，如果是，析构时需要释放

    // ----------- 物理内存：分配、释放、调整大小 -----------

    /// @brief 基类在指定容器中分配内存。子类需要重载。
    /// @param addr 容器所在地址
    /// @param container 容器大小
    /// @param size 实际使用大小。
    /// @return 是否分配成功
    virtual bool alloc(void* addr, size_t container, size_t size) {
        // 注意Addr当前不知道时可以赋0，container不能为0
        ASSERT(container != 0);
        ASSERT(size <= container);
        Addr = static_cast<uint8_t*>(addr);
        containerSize = container;
        memSize = size;
		selfAlloc = false;
        return true;
    }

    /// @brief 如果子类没有重载，默认调用alloc(nullptr, container, size)
    virtual bool alloc(size_t container, size_t size)
    {
        return alloc(nullptr, container, size);
    }

    /// @brief 如果子类没有重载，默认调用alloc(addr, size, size)，而不是alloc(addr, size, 0)
    virtual bool alloc(void* addr, size_t size) {
        return alloc(addr, size, size);
    };

    /// @brief 如果子类没有重载，默认调用alloc(nullptr, size, size)，而不是alloc(nullptr, size, 0)
    virtual bool alloc(size_t size)
    {
        return alloc(nullptr, size, size);
    }

	/// \brief 基类不自动释放内存。子类需要重载实现释放内存。
    virtual void dealloc() {
        Addr = nullptr;
        memSize = 0;
        containerSize = 0; //如果不想containerSize清0，使用resize()函数
        selfAlloc = false;
    }

	// 下面两个函数调整内存大小，区别在于如果原先的内存有多的，一个释放掉，一个不释放只减小memSize。
	
    /// 在容器范围内调整可用内存的大小，多余的释放。
	/// 如果containerSize不够时，基类只返回false
    /// 子类需要重载，决定是重新申请，还是返回false。
    virtual bool resize(size_t size) {
        if (containerSize == 0) //不知道容器大小
            return false;
        if (size <= containerSize) {
            memSize = size;
            return true;
        } else {
            return false;
        }
    };

    /// 在容器范围内调整可用内存的大小，多余的不释放只减小memSize，不释放。
	/// 如果containerSize不够时，基类只返回false
	/// 子类需要重载，决定是重新申请，还是返回false。
    virtual bool reserve(size_t size) {
        if (containerSize == 0) //不知道容器大小
            return false;
        if (size <= containerSize) {
            memSize = size;
            return true;
        } else {
            return false;
        }
    }

    /// \brief 从操作系统的的虚拟空间分配内存，不分配物理内存
	/// \param addr 分配到指定地址。如果不指定地址，请用后面的同名函数更方便，并增加确认内存之前是空的。
	/// \param size 空间大小，会保存到PhysicSize，并设置freeFunc指针为freeInVirtual
	/// \param commit 是否立即分配物理内存
    virtual bool _allocVirtual(void* addr, size_t size, bool commit);

    static uint8_t* allocVirtual(size_t size);
    static void freeVirtual(uint8_t* addr);
protected:
    /// 有些虚拟内存空间需要临时释放，再分拆、合并。
    /// 为避时释放时被别的线程占了，采用以下两个措施：<br>
    /// - 需要分拆的内存规定地址空间从后向前分配。<br>
    /// - 临时释放前加锁，重新占用后解锁
    static std::mutex uplocker;

    /// \brief 从操作系统的的虚拟空间分配内存，不分配物理内存
    /// \param addr 分配到指定地址。如果不指定地址，请用后面的同名函数更方便，并增加确认内存之前是空的。
    /// \param size 空间大小，会保存到PhysicSize，并设置freeFunc指针为freeInVirtual
    /// \param commit 是否立即分配物理内存
    virtual bool _allocUpperVirtual(size_t size, bool commit);
};

/// 统计内存使用的接口类。需要统计内存使用情况的子类继承此类。
/// 内存统计功能没有合并到CMem的原因：
/// 内存的分配、释放时必须功能，而内存统计是个辅助功能。
/// 基类是在现有内存中划一块使用，是不需要统计内存使用量，使其更轻量一点。
class GDCPP_API CMemUsage
    : virtual public CMem // 虚继承CMem，这样在统计表中访问CMemUsage指针，就可以获取其Addr、containerSize和memSize
{
public:
    CMemUsage()
    {
        // 构造时就自动添加到统计表，但未命名
        std::lock_guard guard(MemListLock); // 索引表加锁
        MemList.push_back(this);
    }
	
	virtual ~CMemUsage()
	{
        // 析构时从统计表中删除
        std::lock_guard guard(MemListLock); // 加锁
        for (auto it = MemList.begin(); it != MemList.end(); it++) {
            if (*it == this) {
                MemList.erase(it);
                return ;
            }
        }
	}
	// 不允许复制构造
    CMemUsage(const CMemUsage&) = delete;
	
	/// @brief 被统计的内存块需要起一个名称。构造时为空，需要用户层指定名称，最好不要重名，可以是中文。
    std::wstring memName;

    /// @brief 内存类型名称，通常在子类构造时设定，方便分类统计。
    std::wstring memType;

    /// @brief 统计已经分配的内存ContainerSize总和，调用updateUsage()、report()时更新最新值
    static size_t totalContainerSize;
	
    /// @brief 统计已经分配的内存memSize总和，调用updateUsage()、report()时更新最新值
    static size_t totalMemSize;

    /// @brief 分配的同时命名
 //   virtual bool allocNamed(const wchar_t* name, size_t container, size_t size) = 0;
    virtual bool allocNamed(const std::wstring& name, size_t container, size_t size) = 0;

protected:
    /// @brief 所有已经内配内存索引表，用于统计所有分配的内存
    static std::list<CMemUsage*> MemList;

    /// @brief 索引表添加、删除时加锁
    static std::mutex MemListLock;

    /// @brief 更新统计值。内部调用，未加锁
    static void _updateUsage()
    {
        totalContainerSize = 0;
        totalMemSize = 0;
        for (auto mem : MemList)
        {
            totalContainerSize += mem->containerSize;
            totalMemSize += mem->memSize;
        }
    }

public:
    /// @brief 更新统计值。外部调用，加锁
    static void updateUsage()
    {
        std::lock_guard guard(MemListLock); // 加锁
        _updateUsage();
    }

    /// @brief 输出报表到str
    static void report(std::wstring& str);

    virtual std::wstring details() 
    {
        return std::wstring();
    }
};

class GDCPP_API CWinMem
    : virtual public CMemUsage      // 继承内存统计接口
{
public:
    CWinMem()
    {
        memType = L"Win"; // 标记内存内存类型
    }
    ~CWinMem() override
    {
        dealloc();
    }
    using CMem::alloc; // 防止基类的同名函数被子类掩盖
	
    /// @brief 分配虚拟内存
    /// \param addr 可以为0，由win自动分配一个对齐64k的地址；可以为指定地址，但必须是未分配的。
    /// \param container 虚拟内存大小，必须大于0。内部会对齐到64k
    /// \param size 提交的物理内存大小。size <= container
    /// 可以等于0，之后再用reserve()、resize()提交物理内存。
    /// \return 是否分配成功。
    virtual bool alloc(void* addr, size_t container, size_t size) override;

    /// @brief 分配虚拟内存成功后给它命名为name 
	virtual bool allocNamed(const std::wstring& name, size_t container, size_t size) override
	{
		const bool ret = alloc(nullptr, container, size);
		if (ret)  memName = name;
		return ret;
	}
	
    /// @brief 特殊应用场景从虚拟内存的高端地址往下分配
    bool allocUpper(size_t container, size_t size);
	
    virtual bool resize(size_t size) override;

	virtual bool reserve(size_t size) override;

    virtual void dealloc() override;

    std::wstring details() override;
};

/// 一段内存映射到一个内存映射文件，当访问超出文件顶部时，自动翻转到文件底部；超出底部时自动翻转到顶部。
///
/// <pre>
/// 如下图所示：
///      内存               映像文件
/// -------------
/// |      2     |
/// ==============  ---  ==============
/// |      1     |       |      1     |
/// --------------       --------------
/// |      2     |       |      2     |
/// ==============  ---  ==============
/// |      1     |
/// --------------
/// </pre>
class GDCPP_API CFileMapMem2
    : virtual public CMemUsage
{
public:
    CFileMapMem2()
    {
        memType = L"FileMap"; // 标记内存内存类型
    }
    ~CFileMapMem2()
    {
        dealloc();
    }
    using CMem::alloc; // 防止基类的同名函数被子类掩盖

    /// @brief 执行了以下操作：
    /// - 分配2*container大小的虚拟空间，未分配物理内存。
    /// - 创建了内存映射文件，但未映射到内存
    /// @param name 名字保存到memName，同时加了一个前缀用于创建内存映射文件
    /// @param container 内存映射文件的大小。
    /// @param size 本类此参数为0
    /// @return 
    virtual bool allocNamed(const std::wstring& name, size_t container, size_t size) override final;

    virtual void dealloc() override final;

    /// @brief 调整大小，必须在0和containerSize两个之间调整，不支持其它数值。
    virtual bool resize(size_t size) override final;

    /// @brief 调整大小，必须在0和containerSize两个之间调整，不支持其它数值。
    virtual bool reserve(size_t size) override final
    {
        return resize(size);
    };

    /// @brief 本类必须使用带名称的分配函数，不带名字的直接返回false
    bool alloc(void* addr, size_t container, size_t size) override final { return false; }

protected:
	// 本类自己的Addr、containerSize、memSize保存供外部访问的地址空间
	// 以下3个成员保存整个内存空间、顶部、底部只读访问的映射空间
    CMem allMem;    // 保存整个虚拟内存空间
    CMem TopMem;    // 地址空间在顶部，映射到文件的底部
    CMem BottomMem; // 地址空间在底部，映射到文件的顶部
	
    /// 内存镜像文件的句柄
    HANDLE hand=nullptr;

    /// 共享文件的名称
    std::wstring _nativeKey;

    /// 出错码
    int _error;
    /// 出错信息
    std::wstring _errorString;

    bool mapFile();

    void unmapFile(bool realloc);

public:
    /// @brief 验证内存映射是否正确
    /// @return 
    bool verifyMap(bool compare_content=false);

    std::wstring details() override;
    
};

/**
 * @} //group
*/

#endif // CMem_H
