#include "misc/mapped_file/mapped_file.h"

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

class mapped_file_unix : public mapped_file {
public:
    mapped_file_unix();
    ~mapped_file_unix();

    // 禁用拷贝
    mapped_file_unix(const mapped_file_unix&) = delete;
    mapped_file_unix& operator=(const mapped_file_unix&) = delete;

    // 允许移动
    mapped_file_unix(mapped_file_unix&& other) noexcept;
    mapped_file_unix& operator=(mapped_file_unix&& other) noexcept;

    bool map_file(const std::filesystem::path& filename);
    void unmap();

    [[nodiscard]] const void* get_data() const { return data; }
    [[nodiscard]] void* get_data() { return data; }
    [[nodiscard]] size_t get_size() const { return size; }
    [[nodiscard]] bool is_mapped() const { return data != nullptr; }

private:
    void cleanup();

    void* data;
    size_t size;
    int fd;
};

mapped_file_unix::mapped_file_unix() : data(nullptr), size(0), fd(-1) {}

mapped_file_unix::~mapped_file_unix() {
    cleanup();
}

mapped_file_unix::mapped_file_unix(mapped_file_unix&& other) noexcept
    : data(other.data), size(other.size), fd(other.fd) {
    other.data = nullptr;
    other.size = 0;
    other.fd = -1;
}

mapped_file_unix& mapped_file_unix::operator=(mapped_file_unix&& other) noexcept {
    if (this != &other) {
        cleanup();
        data = other.data;
        size = other.size;
        fd = other.fd;

        other.data = nullptr;
        other.size = 0;
        other.fd = -1;
    }
    return *this;
}

bool mapped_file_unix::map_file(const std::filesystem::path& filename) {
    cleanup();

    std::string utf8_filename(filename.begin(), filename.end());
    fd = open(utf8_filename.c_str(), O_RDONLY);
    if (fd == -1) {
    	// 无法打开文件
        throw std::runtime_error("Failed to open file");
    }

    struct stat sb;
    if (fstat(fd, &sb) == -1) {
        cleanup();
        // 无法获取文件大小
        throw std::runtime_error("Failed to get file size");
    }
    size = static_cast<size_t>(sb.st_size);

    data = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (data == MAP_FAILED) {
        cleanup();
        // 无法映射文件
        throw std::runtime_error("Failed to map file");
    }

    return true;
}

void mapped_file_unix::cleanup() {
    if (data) {
        munmap(data, size);
        data = nullptr;
    }
    if (fd != -1) {
        close(fd);
        fd = -1;
    }
    size = 0;
}

void mapped_file_unix::unmap() {
    cleanup();
}

std::unique_ptr<mapped_file> mapped_file::create() {
	return std::make_unique<mapped_file_unix>();
}
