#include <utility>

#include "kernel_args.h"

#include "kutil/assert.h"
#include "kutil/heap_allocator.h"
#include "kutil/no_construct.h"

#include "frame_allocator.h"
#include "io.h"
#include "log.h"
#include "objects/process.h"
#include "objects/vm_area.h"
#include "vm_space.h"

using memory::frame_size;
using memory::heap_start;
using memory::kernel_max_heap;
using memory::kernel_offset;
using memory::heap_start;
using memory::page_offset;
using memory::pml4e_kernel;
using memory::pml4e_offset;
using memory::table_entries;

using namespace kernel;


// These objects are initialized _before_ global constructors are called,
// so we don't want them to have global constructors at all, lest they
// overwrite the previous initialization.
static kutil::no_construct<kutil::heap_allocator> __g_kernel_heap_storage;
kutil::heap_allocator &g_kernel_heap = __g_kernel_heap_storage.value;

static kutil::no_construct<frame_allocator> __g_frame_allocator_storage;
frame_allocator &g_frame_allocator = __g_frame_allocator_storage.value;

static kutil::no_construct<vm_area_open> __g_kernel_heap_area_storage;
vm_area_open &g_kernel_heap_area = __g_kernel_heap_area_storage.value;

vm_area_buffers g_kernel_stacks {
	memory::kernel_max_stacks,
	vm_space::kernel_space(),
	vm_flags::write,
	memory::kernel_stack_pages};

vm_area_buffers g_kernel_buffers {
	memory::kernel_max_buffers,
	vm_space::kernel_space(),
	vm_flags::write,
	memory::kernel_buffer_pages};

void * operator new(size_t size)           { return g_kernel_heap.allocate(size); }
void * operator new [] (size_t size)       { return g_kernel_heap.allocate(size); }
void operator delete (void *p) noexcept    { return g_kernel_heap.free(p); }
void operator delete [] (void *p) noexcept { return g_kernel_heap.free(p); }

namespace kutil {
void * kalloc(size_t size) { return g_kernel_heap.allocate(size); }
void kfree(void *p) { return g_kernel_heap.free(p); }
}

/*
void walk_page_table(
	page_table *table,
	page_table::level level,
	uintptr_t &current_start,
	size_t &current_bytes,
	vm_area &karea)
{
	constexpr size_t huge_page_size = (1ull<<30);
	constexpr size_t large_page_size = (1ull<<21);

	for (unsigned i = 0; i < table_entries; ++i) {
		page_table *next = table->get(i);
		if (!next) {
			if (current_bytes)
				karea.commit(current_start, current_bytes);
			current_start = 0;
			current_bytes = 0;
			continue;
		} else if (table->is_page(level, i)) {
			if (!current_bytes)
				current_start = reinterpret_cast<uintptr_t>(next);
			current_bytes +=
				(level == page_table::level::pt
					? frame_size
					: level == page_table::level::pd
						? large_page_size
						: huge_page_size);
		} else {
			page_table::level deeper =
				static_cast<page_table::level>(
					static_cast<unsigned>(level) + 1);

			walk_page_table(
				next, deeper, current_start, current_bytes, kspace);
		}
	}
}
*/

void
memory_initialize_pre_ctors(args::header *kargs)
{
	new (&g_kernel_heap) kutil::heap_allocator {heap_start, kernel_max_heap};
	new (&g_frame_allocator) frame_allocator;

	args::mem_entry *entries = kargs->mem_map;
	const size_t count = kargs->map_count;
	for (unsigned i = 0; i < count; ++i) {
		// TODO: use entry attributes
		args::mem_entry &e = entries[i];
		if (e.type == args::mem_type::free)
			g_frame_allocator.free(e.start, e.pages);
	}

	page_table *kpml4 = reinterpret_cast<page_table*>(kargs->pml4);
	process *kp = process::create_kernel_process(kpml4);
	vm_space &vm = kp->space();

	vm_area *heap = new (&g_kernel_heap_area)
		vm_area_open(memory::kernel_max_heap, vm, vm_flags::write);

	vm.add(memory::heap_start, heap);
}

void
memory_initialize_post_ctors(args::header *kargs)
{
	/*
	uintptr_t current_start = 0;
	size_t current_bytes = 0;

	// TODO: Should we exclude the top of this area? (eg, buffers, stacks, etc)
	page_table *kpml4 = reinterpret_cast<page_table*>(kargs->pml4);
	for (unsigned i = pml4e_kernel; i < pml4e_offset; ++i) {
		page_table *pdp = kpml4->get(i);
		kassert(pdp, "Bootloader did not create all kernelspace PDs");

		walk_page_table(
			pdp, page_table::level::pdp,
			current_start, current_bytes,
			g_kernel_space);
	}

	if (current_bytes)
		g_kernel_space.commit(current_start, current_bytes);
	*/
	vm_space &vm = vm_space::kernel_space();
	vm.add(memory::stacks_start, &g_kernel_stacks);
	vm.add(memory::buffers_start, &g_kernel_buffers);

	g_frame_allocator.free(
		reinterpret_cast<uintptr_t>(kargs->page_tables),
		kargs->table_count);

}
