#include "../world.h"
#include "../npc.h"
#include "../player.h"
#include "../aabb.h"
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_keycode.h>
#include <SDL3/SDL_oldnames.h>
#include "../map.h"

void UpdateWorld(F32 delta, World *world)
{
  if(world->bandit.mode == BANDIT_SHOOTOUT){
    delta = delta/4;
  }
  UpdateBandit(delta, &world->bandit, world);
  UpdateNPCs(delta, world);
  PlayerUpdate(delta, &world->player);
}

void UpdateNPCs(F32 delta, World *world)
{
  for (U32 i = 0; i < world->npcCount; i++)
  {
    NPC *npc = &world->npcs[i];
    UpdateNPC(delta, npc, world);
    if (
        world->player.controls.shot && AABB_Slab(world->player.collision.pos, world->player.shotPos, npc->collision) && npc->currentArea == world->player.currentArea)
    {
      printf("You shot %*.s\n", Sv(world->npcs[i].name));
    }
  }
}

void ProcessEvents(SDL_Event *event, World *world)
{
  PlayerInput(event, &world->player);
  if(event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_F5){
    SaveWorld(world);
  }
  if(event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_F6){
    LoadWorld(world->arena, world);
  }
}

void RenderWorld(World *world, D_Context *draw) {
  if(world->player.currentArea == WORLD_AREA_OUTSIDE) {
    for (int i = 0; i < 4800; i++) {
      D_Rect(
        draw,
        (F32) (i % 96), (F32) (i / 96),
        .texture = D_ImageHandle(draw,world->tileTypes[world->map[i]].tag),
        .angle = (F32) world->tileTypes[world->map[i]].rotation,
      );
    }
  }
  for (U32 i = 0; i < world->propCount; i++) {
    if(world->props[i].area == world->player.currentArea) {
      D_Rect(
        draw,
        world->props[i].pos.x,
        world->props[i].pos.y,
        .texture = D_ImageHandle(draw,world->propTypes[world->props[i].propType].tag),
        .scale = world->propTypes[world->props[i].propType].scale,
      );
    }
  }
  for(U32 i = 0; i < world->npcCount; i++) {
    NPC npc = world->npcs[i];
    if(npc.currentArea == world->player.currentArea) {
      NPCDraw(draw, &world->npcs[i]);
    }
  }

  BanditDraw(draw, &world->bandit);
	PlayerDraw(draw, &world->player);
}

#define WRITE(v) FS_FileWrite(file, &(v), sizeof(v), offset); offset += sizeof(v)
#define WRITEN(v, n) FS_FileWrite(file, v, (n) * sizeof(*(v)), offset); offset += ((n) * sizeof(*(v)))

void SaveWorld(World *world) {
  printf("--- Saving World ---\n");
	printf("  - %d props\n", world->propCount);
	printf("  - %d hitboxes\n", world->hitboxCount);

  OS_Handle file = FS_FileOpen(S("world.sgdat"), FS_ACCESS_WRITE);
  U64 offset = 0;

  for (U32 i = 0; i < WORLD_TILE_TYPE_MAX; i++) {
		World_Tile *tile = &world->tileTypes[i];

		WRITE(tile->tag.count);
		WRITEN(tile->tag.data, tile->tag.count);
		WRITE(tile->rotation);
  }

  for (U32 i = 0; i < WORLD_PROP_TYPE_MAX; i++) {
		World_PropType *type = &world->propTypes[i];

		WRITE(type->tag.count);
		WRITEN(type->tag.data, type->tag.count);
		WRITE(type->scale);
  }

	WRITE(world->propCount);
	WRITEN(world->props, WORLD_PROP_MAX);

	WRITE(world->hitboxCount);
	WRITEN(world->hitboxes, WORLD_HITBOX_MAX);

	WRITEN(world->map, WORLD_MAP_MAX);

  WRITE(world->portalCount);
  WRITEN(world->portals, WORLD_PORTAL_MAX);
  FS_FileClose(file);

  printf("--- World Saved ---\n");
}

void LoadWorld(M_Arena *arena, World *world) {
	printf("--- Loading World ---\n");

	M_TempScope(0, 0) {
		Str8 data = FS_ReadEntireFile(temp.arena, S("world.sgdat"));

		U8 *base = data.data;

		// Load tile types
		//
		world->tileTypes = M_ArenaPush(arena, World_Tile, .count = WORLD_TILE_TYPE_MAX);
		for (U32 it = 0; it < WORLD_TILE_TYPE_MAX; ++it) {
			World_Tile *type = &world->tileTypes[it];

			Str8 name;
			name.count = *(S64 *) base;
			name.data  = base + 8;

			base += 8;
			base += name.count;

			if (name.count != 0) {
				// Means we likely got a vaild tag
				type->tag      = Str8_Copy(arena, name);
				type->rotation = *(F32 *) base;
			}

			base += 4;
		}

		// Load prop types
		//
		world->propTypes = M_ArenaPush(arena, World_PropType, .count = WORLD_PROP_TYPE_MAX);
		for (U32 it = 0; it < WORLD_PROP_TYPE_MAX; ++it) {
			World_PropType *type = &world->propTypes[it];

			Str8 name;
			name.count = *(S64 *) base;
			name.data  = base + 8;

			base += 8;
			base += name.count;

			if (name.count != 0) {
				type->tag    = Str8_Copy(arena, name);
				type->scale  = *(F32 *) base;
			}

			base += 4;
		}

		// Prop locations
		{
			U32 n_props = *(U32 *) base; base += 4;
			World_Prop *props = (World_Prop *) base;

			world->propCount = n_props;
			world->props     = M_ArenaPush(arena, World_Prop, .count = WORLD_PROP_MAX);

			M_CopySize(world->props, props, n_props * sizeof(World_Prop));

			base += (WORLD_PROP_MAX * sizeof(World_Prop));
		}

		// Hitboxes
		//
		{
			U32 n_hitbox = *(U32 *) base; base += 4;
			World_Hitbox *boxes  = (World_Hitbox *) base;

			world->hitboxCount = n_hitbox;
			world->hitboxes    = M_ArenaPush(arena, World_Hitbox, .count = WORLD_HITBOX_MAX);

			M_CopySize(world->hitboxes, boxes, n_hitbox * sizeof(World_Hitbox));

			base += (WORLD_HITBOX_MAX * sizeof(World_Hitbox));
		}

		// Map
		//
		{
			U32 *__map = (U32 *) base;

			world->map = M_ArenaPush(arena, U32, .count = WORLD_MAP_MAX);
			M_CopySize(world->map, __map, WORLD_MAP_MAX * sizeof(U32));

			base += (WORLD_MAP_MAX * sizeof(U32));
		}
		{
			U32 n_portals = *(U32 *) base; base += 4;
			World_Portal *portals  = (World_Portal *) base;

			world->portalCount = n_portals;
			world->portals    = M_ArenaPush(arena, World_Portal, .count = WORLD_PORTAL_MAX);

			M_CopySize(world->portals, portals, n_portals * sizeof(World_Portal));

			base += (WORLD_PORTAL_MAX * sizeof(World_Portal));
		}

		if (base != (data.data + data.count)) {
			printf("--- OFFSET MISMATCH ---\n");
		}

    printf("loaded world\n");

		printf("  - %d props\n", world->propCount);
		printf("  - %d hitboxes\n", world->hitboxCount);

		printf("--- Loaded World ---\n");
	}
}

void GenerateNavMesh(M_Arena *arena, World *world) {
  world->navMesh = M_ArenaPush(arena, NavMesh);
  world->navMesh->nodeCount = 0;
  for(int i = 0; i < WORLD_MAP_MAX; i++) {
    U32 x = (i % 96);
    U32 y = (i / 96);
    bool skip = false;
    for(U32 hi = 0; hi < world->hitboxCount; hi++) {
      if(AABB_Point(world->hitboxes[hi].box, V2F((F32) x, (F32) y))) {
        skip = true;
        break;
      }
    }
    if(skip) {continue;}
    world->navMesh->nodes[world->navMesh->nodeCount].pos.x = x;
    world->navMesh->nodes[world->navMesh->nodeCount].pos.y = y;
    world->navMesh->nodes[world->navMesh->nodeCount].connectionCount = 0;
    for(int nx = -1; nx < 2; nx++){
      for(int ny = -1; ny < 2; ny++){
        if(nx==0 && ny==0) {continue;}
        if(x+nx < 0 || x+nx > 95) {
          continue;
        }
        if(y+ny < 0 || y+ny > 49) {
          continue;
        }
        // It's quad for loop time :D
        for(U32 hi = 0; hi < world->hitboxCount; hi++) {
          if(AABB_Point(world->hitboxes[hi].box, V2F((F32) (x + nx), (F32) (y + ny)))) {
            skip = true;
            break;
          }
        }
        U32 nCount = world->navMesh->nodeCount;
        S32 index = -1;
        for(int ohgod = 0; ohgod < nCount; ohgod++) {
          if(world->navMesh->nodes[ohgod].pos.x == nx+x&& world->navMesh->nodes[ohgod].pos.y == y+ny) {
            index=ohgod;
            break;
          }
        }
        F32 cost = 20;
        if(ny+nx == 2) {
          cost = 40;
        };
        if(Str8_Equal(world->tileTypes[world->map[i]].tag, S("path_middle"), STR8_EQUAL_IGNORE_CASE)) {
          cost = 1;
          if(Abs(ny+nx) == 2) {
            cost = 4;
          };
        }
        if(index < 0) {continue;}
        skip |= index > nCount;
        if(skip) {continue;}
        world->navMesh->nodes[nCount].connections[world->navMesh->nodes[nCount].connectionCount].NodeIndex = index;
        world->navMesh->nodes[nCount].connections[world->navMesh->nodes[nCount].connectionCount].Cost = cost;
        world->navMesh->nodes[index].connections[world->navMesh->nodes[index].connectionCount].NodeIndex = nCount;
        world->navMesh->nodes[index].connections[world->navMesh->nodes[index].connectionCount].Cost = cost;
        world->navMesh->nodes[nCount].connectionCount++;
        world->navMesh->nodes[index].connectionCount++;
      }
    }
    world->navMesh->nodeCount++;
  }
  for(int i = 0; i < world->npcCount; i++) {
      world->npcs[i].mode = NPC_ACTION_WAITING;
      world->npcs[i].maxWaitTime = Random_F32(&world->random, 20, 140);
      world->npcs[i].waitTime = 0;
      world->npcs[i].currentNavNode=0; 
      world->npcs[i].pathIndex = 0;
  }
}
