#include "world.h"
#include "game.h"
#include "entity.h"
#include "killLog.h"
#include "entitiesInclude.h"

void initWorld(World * world) {
	world->entities = NULL;
	world->entitiesCount = 0;

	world->lookUp = NULL;
	world->lookUpSize = 0;

	world->vacantIds = NULL;
	world->vacantIdsCount = 0;

	world->entitiesToAdd = NULL;
	world->entitiesToAddCount = 0;

	// Set current fingerprint.
	SetRandomSeed(time(NULL));

	world->currentFingerprint = GetRandomValue(
		FINGERPRINT_START_RANGE_MIN,
		FINGERPRINT_START_RANGE_MAX 
	);
}

void freeWorld(World * world) {
	int i;

	if (world->entities == NULL)
		return;

	// Close entities.
	for (i = 0; i < world->entitiesCount; ++i)
		closeEntity(&world->entities[i]);

	KF_FREE(world->entities);
	KF_FREE(world->lookUp);

	if (world->vacantIds != NULL)
		KF_FREE(world->vacantIds);

	world->entities = NULL;
}

// Not for direct use.
KfError pushVacantId(World * world, EntityId id) {
	++world->vacantIdsCount;

	// Allocate.
	if (world->vacantIds == NULL)
		world->vacantIds = (EntityId*)KF_CALLOC(1, sizeof(EntityId));
	else
		world->vacantIds = (EntityId*)KF_REALLOCARRAY(
			world->vacantIds,
			world->vacantIdsCount, 
			sizeof(EntityId)
		);

	if (world->vacantIds == NULL) {
		ALLOCATION_ERROR;
		return KFERROR;
	}

	world->vacantIds[world->vacantIdsCount - 1] = id;
	return KFSUCCESS;
}

// Not for direct use.
EntityId popVacantId(World * world) {
	EntityId id;

	// Already empty.
	if (world->vacantIds == NULL)
		return ENTITY_NONE;

	id = world->vacantIds[world->vacantIdsCount - 1];

	// Decrease count.
	--world->vacantIdsCount;

	// Free or recallocate.
	if (world->vacantIdsCount == 0) {
		KF_FREE(world->vacantIds);
		world->vacantIds = NULL;
	} else {
		world->vacantIds = (EntityId*)KF_REALLOCARRAY(
			world->vacantIds,
			world->vacantIdsCount, 
			sizeof(EntityId)
		);
	}

	return id;
}

Entity * getEntityFromWorld(World world, EntityId id) {
	if (world.entities == NULL || id < 0 || id >= world.lookUpSize)
		return NULL;

	int pos = world.lookUp[id];

	if (pos == ENTITY_ID_NONE)
		return NULL;

	return &world.entities[pos];
}

EntityId addEntityToWorld(World * world, Entity entity) {
	EntityId id;
	EntityId poppedId;

	Entity entityCopy = entity;

	// Set fingerprint.
	entityCopy.fingerprint = world->currentFingerprint;
	++world->currentFingerprint;

	// Not allocated.
	if (world->entities == NULL) {
		// Allocate.
		world->entities = (Entity*)KF_CALLOC(1, sizeof(Entity));
		world->lookUp = (EntityId*)KF_CALLOC(1, sizeof(EntityId));
		world->entitiesCount = 1;
		world->lookUpSize = 1;

		if (world->entities == NULL || world->lookUp == NULL) {
			ALLOCATION_ERROR;
			return ENTITY_NONE;
		}

		// Set entity and id.
		world->entities[0] = entityCopy;
		world->entities[0].id = 0;
		world->lookUp[0] = 0;
		return 0;
	}

	++world->entitiesCount;

	// Resize.
	world->entities = (Entity*)KF_REALLOCARRAY(
		world->entities,
		world->entitiesCount,
		sizeof(Entity)
	);

	if (world->entities == NULL) {
		ALLOCATION_ERROR;
		return ENTITY_NONE;
	}

	// Set entity.
	world->entities[world->entitiesCount - 1] = entityCopy;

	// Set look up.
	poppedId = popVacantId(world);

	// Got popped id.
	if (poppedId != ENTITY_ID_NONE) {
		world->lookUp[poppedId] = world->entitiesCount - 1;
		world->entities[world->entitiesCount - 1].id = poppedId;
		return poppedId;
	}

	++world->lookUpSize;

	world->lookUp = (EntityId*)KF_REALLOCARRAY(
		world->lookUp,
		world->lookUpSize,
		sizeof(EntityId)
	);

	if (world->lookUp == NULL) {
		ALLOCATION_ERROR;
		return ENTITY_NONE;
	}

	// Set id.
	id = world->lookUpSize - 1;
	world->entities[world->entitiesCount - 1].id = id;
	world->lookUp[id] = world->entitiesCount - 1;
	return id;
}

KfError removeEntityFromWorld(World * world, EntityId id) {
	int i;
	int pos;

	if (world->entitiesCount == 1)
		return KFERROR;

	// Get position in list.
	pos = world->lookUp[id];

	if (pos == ENTITY_ID_NONE)
		return KFSUCCESS;

	// Close entity.
	closeEntity(&world->entities[pos]);

	// Push id.
	if (pushVacantId(world, id) != KFSUCCESS)
		return KFERROR;

	for (i = pos; i < world->entitiesCount - 1; ++i) {
		// Move back entities
		world->entities[i] = world->entities[i + 1];

		// Update lookUp.
		world->lookUp[world->entities[i].id] = i;
	}

	world->lookUp[id] = ENTITY_ID_NONE;

	// Resize entities.
	--world->entitiesCount;

	world->entities = (Entity*)KF_REALLOCARRAY(
		world->entities,
		world->entitiesCount,
		sizeof(Entity)
	);

	if (world->entities == NULL) {
		ALLOCATION_ERROR;
		return KFERROR;
	}

	return KFSUCCESS;
}

KfError scheduleEntityToAdd(World * world, Entity entity) {
	++world->entitiesToAddCount;

	// Allocate shit.
	if (world->entitiesToAdd == NULL)
		world->entitiesToAdd = (Entity*)KF_CALLOC(world->entitiesToAddCount, sizeof(Entity));
	else
		world->entitiesToAdd = (Entity*)KF_REALLOCARRAY(
			world->entitiesToAdd,
			world->entitiesToAddCount,
			sizeof(Entity)
		);

	if (world->entitiesToAdd == NULL) {
		ALLOCATION_ERROR;
		return KFERROR;
	}

	world->entitiesToAdd[world->entitiesToAddCount - 1] = entity;
	return KFSUCCESS;
}

KfError handleScheduledEntities(World * world) {
	int i;

	// No entities to add.
	if (world->entitiesToAdd == NULL)
		return KFSUCCESS;

	for (i = 0; i < world->entitiesToAddCount; ++i) {
		// Add entity indeed.
		if (addEntityToWorld(world, world->entitiesToAdd[i]) == ENTITY_NONE)
			return KFERROR;
	}

	// Clean up this shit.
	KF_FREE(world->entitiesToAdd);
	world->entitiesToAdd = NULL;
	world->entitiesToAddCount = 0;

	return KFSUCCESS;
}

void handleCollisionInWorld(Entity * entity1, Entity * entity2) {
	//if (entity1->id != 0)
	//	entity1->health = 0.0;
	//if (entity2->id != 0)
	//	entity2->health = 0.0;
	

	if (entity1->type != ENTITY_ANTIFA && entity2->type != ENTITY_ANTIFA)
		setSoldatoLeader(entity1, entity2);
	
	switch (entity1->type) {
		case ENTITY_ANTIFA:
			break;
		case ENTITY_SOLDATO:
			break;
		case ENTITY_CAPORALE:
			break;
		case ENTITY_SERGENTE:
			break;
		case ENTITY_MARESCIALLO:
			break;
		case ENTITY_GENERALE:
			break;
		case ENTITY_MUSSOLINI:
			break;
		case ENTITY_GUIDED_MISSILE:
			break;
		default:
			break;
	}
}

void updateWorld(World * world, Game * game) {
	int i, j;
	Entity * entity;
	Entity * entity2;

	// People are fucking dying.
	EntityId kills[world->entitiesCount];
	size_t killCount = 0;

	for (i = 0; i < world->entitiesCount; ++i) {
		entity = &world->entities[i];

		// Call update callback.
		if (entity->updateCb != NULL)
			entity->updateCb(game, entity);

		// Check for collision.
		for (j = 0; j < i; ++j) {
			entity2 = &world->entities[j];

			// Collided.
			// Only use real collision if player is there.
			if (entity->id == ENTITY_ANTIFA || entity2->id == ENTITY_ANTIFA) {
				if (checkEntityCollision(entity, entity2))
					handleCollisionInWorld(entity, entity2);
			} else {
				if (Vector3Distance(entity->position, entity2->position) <=
					entity->radius + entity2->radius)
					handleCollisionInWorld(entity, entity2);
			}
		}

		// It fucking died.
		if (entity->health <= 0.0 && entity->type != ENTITY_ANTIFA) {
			kills[killCount] = entity->id;
			++killCount;
		}
	}

	// "bring out your dead!"
	for (i = 0; i < killCount; ++i) {
		pushKill(&game->killLog, *getEntityFromWorld(*world, kills[i]));
		removeEntityFromWorld(world, kills[i]);
	}

	// Handle some shit.
	handleScheduledEntities(world);
}

void drawWorld(World * world, Game * game) {
	int i;
	Entity * entity;

	for (i = 0; i < world->entitiesCount; ++i) {
		entity = &world->entities[i];

		// Call draw callback.
		if (entity->drawCb != NULL)
			entity->drawCb(game, entity);
	}
}


EntityId traceRayToEntityInWorld(World * world, Ray ray, EntityFingerprint from, bool useFrom) {
	int i, j;
	RayCollision collision;
	Entity * currentEntity;
	Ray transformedRay;

	// Set direction.
	transformedRay.direction = ray.direction;

	// Used for finding closest.
	float closest = -1.0; // -1.0 means ray haven't hit anything.
	EntityId closestId = ENTITY_NONE;

	// Loop through entities.
	for (i = 0; i < world->entitiesCount; ++i) {
		currentEntity = &world->entities[i];

		if (currentEntity->fingerprint == from && useFrom)
			continue;
		else if (currentEntity->model == NULL) // Null model indeed.
			continue;

		// Set position relative to entity.
		transformedRay.position = Vector3Subtract(ray.position, currentEntity->position);

		// Loop through meshes.
		for (j = 0; j < currentEntity->model->meshCount; ++j) {
			collision = GetRayCollisionMesh(
				transformedRay,
				currentEntity->model->meshes[j],
				currentEntity->model->transform
			);

			// Did hit.
			if (collision.hit) {
				// Find closest.
				if (collision.distance < closest || closest == -1.0) {
					closest = collision.distance;
					closestId = currentEntity->id;
				}
			}
		}
	}

	return closestId;
}

void debugWorld(World * world) {
	int i;
	Entity * entity;

	for (i = 0; i < world->entitiesCount; ++i) {
		entity = &world->entities[i];

		bool idMatches = world->lookUp[entity->id] == i;

		printf("index: %d, look up: %d, id: %d fingerprint: %x, matches: %d\n", i, world->lookUp[entity->id], entity->id, entity->fingerprint, idMatches);
	}

	// for (i = 0; i < world->lookUpSize; ++i) {
	// 	if (world->lookUp[i] != ENTITY_ID_NONE)
	// 		printf("%d\n", world->entities[world->lookUp[i]].id == i);
	// }

	if (world->vacantIds == NULL)
		return;

	for (i = 0; i < world->vacantIdsCount; ++i)
		printf("v %d\n", world->vacantIds[i]);

	puts("");
}

KfError addEntryToWorld(World * world, Game * game, WorldEntry entry) {
	// Create entity.
	Entity entity = createEntity(entry.type, game);
	entity.position = entry.position;
	entity.rotation = entry.rotation;

	// Add to world.
	if (addEntityToWorld(world, entity) == ENTITY_NONE)
		return KFERROR;

	return KFSUCCESS;
}

KfError addEntriesToWorld(World * world, Game * game, WorldEntry * entries, size_t entriesCount) {
	int i;

	for (i = 0; i < entriesCount; ++i)
		if (addEntryToWorld(world, game, entries[i]) != KFSUCCESS)
			return KFERROR;

	return KFSUCCESS;
}