#include "state.h"

#include <stdlib.hfa>

//general purpouse includes
#include "tools.h"

//platform abstraction includes
#include "allocate-pool.h"

//gc internal includes
#include "collector.h"
#include "globals.h"
#include "memory_pool.h"
#include "object_header.h"
#include "tools/worklist.h"

void gc_state_swap(gc_state *const this);
void gc_state_sweep_roots(gc_state *const this, worklist_t* worklist);
void gc_state_clear(gc_state *const this);
void gc_state_calc_usage(gc_state *const this);

#ifndef NDEBUG
	bool gc_state_roots_match(gc_state *const this);
	bool gc_state_no_from_space_ref(gc_state *const this);
#endif

static gc_state s;

gc_state* gc_get_state()
{
	if(!s.is_initialized) ctor(&s);
	return &s;
}

void ctor(gc_state *const this)
{
	this->from_code = 0;
	this->to_space = NULL;
	this->from_space = NULL;
	this->total_space = 0;
	this->used_space = 0;
	ctor(&this->pools_table);

	gc_allocate_pool(this);

	this->is_initialized = true;
}

void dtor(gc_state *const this)
{
	dtor(&this->pools_table);
	this->is_initialized = false;
}

bool gc_is_in_heap(const gc_state* const this, const void* const address)
{
	gc_memory_pool* target_pool = gc_pool_of(address);

	gc_memory_pool** first = cbegin(&this->pools_table);
	gc_memory_pool** last = cend(&this->pools_table);
	gc_memory_pool** result = find(first, &last, target_pool);
	return result != last && gc_pool_is_from_space(*result);
}

bool gc_is_in_to_space(const gc_state* const this, const void* const address)
{
	gc_memory_pool* target_pool = gc_pool_of(address);

	gc_memory_pool** first = cbegin(&this->pools_table);
	gc_memory_pool** last = cend(&this->pools_table);
	gc_memory_pool** result = find(first, &last, target_pool);
	return result != last && !gc_pool_is_from_space(*result);
}

gc_object_header* gc_get_object_for_ref(gc_state* state, void* member)
{
	volatile int stage = 0;
	intptr_t target = ((intptr_t)member);
	if(!gc_is_in_heap(state, member)) return NULL;
	stage++;

	gc_memory_pool* pool = gc_pool_of(member);
	stage++;
	gc_pool_object_iterator it = gc_pool_iterator_for(pool, member);
	stage++;
	gc_pool_object_iterator end = end(pool);
	stage++;

	while(it != end)
	{
		gc_object_header* object = *it;
		check(object);
		check( is_valid(object) );
		{
			intptr_t start = ((intptr_t)object);
			intptr_t end = ((intptr_t)start + object->size);
			if(start < target && end > target)
			{
				return object;
			}
		}
		stage++;
		++it;
	}

	checkf( (int) 0, "is_in_heap() and iterator_for() return inconsistent data");
	abort();
	return NULL;
}

void* gc_try_allocate(gc_state* const this, size_t size)
{
	gc_memory_pool* pool = this->from_space;
	while(pool != (gc_memory_pool*)0)
	{
		if(gc_pool_size_left(pool) > size)
		{
			return gc_pool_allocate(pool, size, true);
		}
		pool = pool->next;
	}

	return (void*)0;
}

void gc_allocate_pool(gc_state *const this)
{
	gc_memory_pool* old_from_space = this->from_space;
      gc_memory_pool* old_to_space = this->to_space;

      this->from_space = (gc_memory_pool*)(pal_allocPool(POOL_SIZE_BYTES, 1));
      this->to_space   = (gc_memory_pool*)(pal_allocPool(POOL_SIZE_BYTES, 1));

      this->from_space{ POOL_SIZE_BYTES, old_from_space, this->to_space,   this->from_code };
      this->to_space  { POOL_SIZE_BYTES, old_to_space,   this->from_space, (~this->from_code) & 0x01 };

	this->total_space += gc_pool_size_used(this->from_space);

	push_back(&this->pools_table, this->from_space);
	push_back(&this->pools_table, this->to_space);
}

void gc_collect(gc_state* const this)
{
	// DEBUG("collecting");
	// DEBUG("previous usage " << this->used_space << " / " << this->total_space);

	worklist_t worklist;
	ctor(&worklist);
	gc_state_sweep_roots(this, &worklist);

	while(!empty(&worklist))
	{
		intptr_t* ref = back(&worklist);
		pop_back(&worklist);
		gc_process_reference((void**)ref, &worklist);
	}

	check(gc_state_roots_match(this));
	check(gc_state_no_from_space_ref(this));

	gc_state_swap(this);

	gc_state_calc_usage(this);

	if(gc_needs_collect(this)) gc_allocate_pool(this);

	// DEBUG("done");
	dtor(&worklist);
}

void gc_state_swap(gc_state* const this)
{
	swap(&this->from_space, &this->to_space);

	gc_memory_pool* pool = this->to_space;
	while(pool)
	{
		gc_reset_pool(pool);
		pool = pool->next;
	}

	this->from_code = (~this->from_code) & 0x01;

	#ifndef NDEBUG
		{
			gc_memory_pool* pool = this->from_space;
			while(pool)
			{
				check(gc_pool_is_from_space(pool));
				pool = pool->next;
			}

			pool = this->to_space;
			while(pool)
			{
				check(!gc_pool_is_from_space(pool));
				pool = pool->next;
			}
		}
	#endif
}

void gc_state_sweep_roots(gc_state* const this, worklist_t* worklist)
{
	gc_memory_pool* pool = this->from_space;
	while(pool)
	{
		gc_pool_object_iterator it = begin(pool);
		gc_pool_object_iterator end = end(pool);
		for(;it != end; ++it)
		{
			gc_object_header* object = *it;
			if(!object->root_chain) continue;

			gc_copy_object(object);

			gc_scan_object(object->forward, worklist);
		}

		pool = pool->next;
	}
}

void gc_state_clear(gc_state* const this)
{
	gc_memory_pool* pool = this->from_space;
	while(pool)
	{
		gc_reset_pool(pool);
		pool = pool->next;
	}

	pool = this->to_space;
	while(pool)
	{
		gc_reset_pool(pool);
		pool = pool->next;
	}
}

void gc_state_calc_usage(gc_state* const this)
{
	this->total_space = 0;
	this->used_space = 0;

	gc_memory_pool* pool = this->from_space;
	while(pool)
	{
		size_t size = gc_pool_size_total(pool);
		size_t used = gc_pool_size_used(pool);
		check(used <= size);
		this->total_space += size;
		this->used_space += used;

		pool = pool->next;
	}
}

#ifndef NDEBUG
	bool gc_state_roots_match(gc_state* const this)
	{
		gc_memory_pool* pool = this->to_space;
		while(pool)
		{
			size_t size = 0;
			gc_pool_object_iterator it = begin(pool);
			gc_pool_object_iterator end = end(pool);
			for(;it != end; ++it)
			{
				gc_object_header* object = *it;
				size += object->size;

				gcpointer_t* ptr = object->root_chain;
				while(ptr)
				{
					check(gc_get_object_ptr( (void*)ptr->ptr ) == object);
					ptr = ptr->next;
				}
			}

			checkf(size + gc_pool_size_left(pool) == gc_pool_size_total(pool),
				(const char*)"expected %lu + %lu == %lu\n",
				(size_t)size,
				(size_t)gc_pool_size_left(pool),
				(size_t)gc_pool_size_total(pool));

			pool = pool->next;
		}

		return true;
	}

	bool gc_state_no_from_space_ref(gc_state* const this)
	{
		gc_memory_pool* pool = this->to_space;
		while(pool)
		{
			void** potential_ref = (void**)pool->start_p;
			while(potential_ref < (void**)pool->free_p)
			{
				check(!gc_is_in_heap(this, *potential_ref));
				potential_ref++;
			}

			pool = pool->next;
		}

		return true;
	}
#endif
