#pragma once
#include <stdint.h>
#include <endian.hpp>
#include <string>
#include <iostream>
#include <assert.h>

// Unix-y stuff
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>

struct errno_exception : public std::runtime_error {
	errno_exception(int err)
	: runtime_error{std::string{"errno with value "}+std::to_string(err)}
	{}
};

void throw_errno_if(bool pred)
{
	if(pred)
	{
		throw errno_exception{errno};
	}
}

constexpr size_t page_size = 4096;

struct [[gnu::packed]] record_identifier{
	bitops::regulated<uint32_t> x = 0;
	bitops::regulated<uint32_t> y = 0;
	std::array<uint8_t, 16> uuid = {0};
};

template<>
struct std::hash<record_identifier> {
	uint64_t operator() (const record_identifier& value) {
		uint64_t v = static_cast<uint32_t>(value.x);
		
		v <<= 4;
		v += static_cast<uint32_t>(value.y);
		v ^= *(uint64_t*)&value.uuid;
		v ^= *(((uint64_t*)&value.uuid)+1);

		return v;
	}
};

enum class record_flags : uint32_t {
	confirmation = 1
};

struct [[gnu::packed]] record{
	record()
	{}
	union [[gnu::packed]] {
		std::array<uint8_t, 24> full;
		record_identifier split = record_identifier();
	} record_head;
	bitops::regulated<uint64_t> timestamp = 0;
	bitops::regulated<uint64_t> offset = 0;
	bitops::regulated<uint32_t> flags = 0;
};

constexpr size_t db_page_size = 16384;
using db_page = std::array<uint8_t, db_page_size>;

template<typename T>
struct mmap_ptr{
	T* start = nullptr;
	size_t offset = 0;

	T& operator*(){
		return start[offset];
	}

	T& operator[](const size_t& n){
		return start[offset+n];
	}

	mmap_ptr operator+(size_t offset) {
		mmap_ptr ret;
		ret.start = start;
		ret.offset = offset + this->offset;
		return ret;
	}

	mmap_ptr operator++() {
		offset+=1;
		mmap_ptr ret = *this;
		return ret;
	}

	mmap_ptr operator++(int) {
		mmap_ptr ret = *this;
		offset+=1;
		return ret;
	}

	bool operator==(const mmap_ptr& oth) {
		return (start == oth.start) && (offset == oth.offset);
	}

	bool operator!=(const mmap_ptr& oth) {
		return !(*this == oth);
	}
};

template<typename T>
class mmap_array : public mmap_ptr<T>
{
	size_t mapping_size = 0;
	int fd = 0;

	size_t item_size = 0;

	public:
	mmap_array() = default;

	mmap_array(size_t nb_elem, const std::string& filename)
	: mmap_ptr<T>() {
		const size_t expected_size = nb_elem*sizeof(T);
		const size_t expected_size_mod = expected_size + (
			(expected_size%page_size != 0)?
				page_size - expected_size%page_size
				: 0 
		);
		fd = open(
			filename.c_str(), 
			O_RDWR | O_CREAT, 
			S_ISUID + S_ISGID + S_IRUSR + S_IWUSR + S_IRGRP + S_IWGRP
		);
		throw_errno_if(fd < 0);

		struct stat data;
		int status = fstat(fd, &data);
		throw_errno_if(status < 0);

		if((size_t)data.st_size < expected_size_mod)
		{
			status = ftruncate(fd, expected_size_mod);
			throw_errno_if(status < 0);
		}

		this->start = (T*)mmap(NULL, expected_size_mod, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
		throw_errno_if(this->start == nullptr);
		mapping_size = expected_size_mod;
		item_size = mapping_size / sizeof(T);

		auto map_access = MADV_RANDOM;

		if(expected_size_mod <= 1 << 20) {
			map_access |= MADV_WILLNEED;
		}
		
		#ifdef MADV_DONTDUMP
			map_access |= MADV_DONTDUMP;
		#endif

		this->offset = 0;

		posix_madvise(this->start, mapping_size, map_access);
	}

	void enforce_caching() {
		posix_madvise(this->start, mapping_size, MADV_WILLNEED);
	}

	constexpr size_t size() {
		return item_size;
	}

	int fd_v() const {
		return fd;
	}

	constexpr mmap_ptr<T> begin() const {
		mmap_ptr<T> ret = *this;
		return ret;
	}

	constexpr mmap_ptr<T> end() const {
		mmap_ptr<T> ret = *this;
		return ret+item_size;
	}

	void clear() {
		munmap(this->start, mapping_size);
		close(fd);
	}

	~mmap_array() {
		//std::cerr << "unmapping disabled" << std::endl;
		if(false)//this->start != nullptr)
		{
			munmap(this->start, mapping_size);
			close(fd);
		}
	}
};

