/**
 * Copyright (c) 2021 Dominic Masters
 * 
 * This software is released under the MIT License.
 * https://opensource.org/licenses/MIT
 */

#include "map.h"

map_t MAP_STATE;

void mapInit() {
  int32_t i, z, x, y;
  chunk_t *chunk;

  // Reset map
  MAP_STATE.x = MAP_STATE.y = MAP_STATE.z = 0;

  // Load the texture
  MAP_STATE.texture = assetTextureLoad(MAP_ASSET_TEXTURE);

  // Load the tileset
  MAP_STATE.tileset = tilesetCreate(
    MAP_STATE.texture->width/TILE_WIDTH, MAP_STATE.texture->height/TILE_HEIGHT,
    MAP_STATE.texture->width, MAP_STATE.texture->height,
    0, 0, 0, 0
  );

  // Prepare the tile definitions
  MAP_STATE.tileDefinitions = calloc(
    MAP_STATE.tileset->count,sizeof(tiledef_t)
  );

  // TODO: Load the tile definitions here.
  for(i = 0; i < MAP_STATE.tileset->count; i++) {
    (MAP_STATE.tileDefinitions+i)->verticeCount = 4;
    (MAP_STATE.tileDefinitions+i)->indiceCount = 6;
  }

  // Load each of the chunks. The ZYX order is important for ordering.
  i = 0;
  for(z = 0; z < MAP_DEPTH; z++) {
    for(y = 0; y < MAP_HEIGHT; y++) {
      for(x = 0; x < MAP_WIDTH; x++) {
        chunk = MAP_STATE.chunks + i;
        MAP_STATE.chunkList[i] = chunk;

        // Load initial chunk state.
        chunk->x = x;
        chunk->y = y;
        chunk->z = z;
        chunk->primitive = NULL;

        // Load the chunk.
        chunkLoad(chunk, x, y, z);
        i++;
      }
    }
  }
}

void mapRender() {
  int32_t i;
  float x, y, z;
  chunk_t *chunk;

  // Bind the texture
  shaderUseTexture(GAME_STATE.shaderWorld, MAP_STATE.texture);

  // Render each chunk.
  // TODO: Do we need to render every chunk? (screen area)
  for(i = 0; i < MAP_CHUNK_COUNT; i++) {
    chunk = MAP_STATE.chunkList[i];
    if(chunk->primitive == NULL) continue;

    // Offset the primitive (into world space)
    x = (chunk->x * CHUNK_WIDTH);
    y = (chunk->y * CHUNK_HEIGHT);
    z = (chunk->z * CHUNK_DEPTH);
    shaderUsePosition(GAME_STATE.shaderWorld, x, y, z, 0, 0, 0);
    primitiveDraw(chunk->primitive, 0, chunk->primitive->indiceCount);
  }
}

void mapDispose() {
  int32_t i;
  chunk_t *chunk;

  // Unload the chunks
  for(i = 0; i < MAP_CHUNK_COUNT; i++) {
    chunk = MAP_STATE.chunkList[i];
    chunkUnload(chunk);
  }

  tilesetDispose(MAP_STATE.tileset);
  textureDispose(MAP_STATE.texture);
}

void mapShift(int32_t x, int32_t y, int32_t z) {
  int32_t i,
    /** New List Coordinates */
    lx, ly, lz,
    /** New Local Chunk Coordinates */
    nx, ny, nz,
    /** New Absolute Chunk Coordinates */
    ax, ay, az,
    /** New Chunk Index */
    ni,
    /** Precalculated width * height */
    wh
  ;
  chunk_t *chunk;
  chunk_t *chunkList[MAP_CHUNK_COUNT];

  // Calculate the new chunklist coordinates
  lx = MAP_STATE.x + x;
  ly = MAP_STATE.y + y;
  lz = MAP_STATE.z + z;

  // Precalc width * height.
  wh = MAP_WIDTH * MAP_HEIGHT;

  for(i = 0; i < MAP_CHUNK_COUNT; i++) {
    chunk = MAP_STATE.chunkList[i];

    // Calculate the new local positions for the chunk.
    nx = mathMod(chunk->x - MAP_STATE.x - x, MAP_WIDTH);
    ny = mathMod(chunk->y - MAP_STATE.y - y, MAP_HEIGHT);
    nz = mathMod(chunk->z - MAP_STATE.z - z, MAP_DEPTH);

    // Load the chunk if we need to. We also use this to calculate new absolutes
    if(
      (ax = lx + nx) != chunk->x ||
      (ly + ny) != chunk->y ||
      (lz + nz) != chunk->z
    ) {
      // Calculate those things that may have not been done within the if
      ay = ly + ny;
      az = lz + nz;
      
      // Load new chunk.
      chunkUnload(chunk);
      chunkLoad(chunk, ax, ay, az);

      // Update the absolute coordinates.
      chunk->x = ax;
      chunk->y = ay;
      chunk->z = az; 
    }

    // Now, based off those new local positions, calculate the new index.
    ni = (
      nx +
      (ny * MAP_WIDTH) +
      (nz * wh)
    );
    chunkList[ni] = chunk;
  }

  // Update Absolutes.
  MAP_STATE.x = lx;
  MAP_STATE.y = ly;
  MAP_STATE.z = lz;

  // Now copy that array over.
  memcpy(MAP_STATE.chunkList, chunkList, sizeof(chunk_t *) * MAP_CHUNK_COUNT);
}

void mapAlign(int32_t x, int32_t y, int32_t z) {
  mapShift(x - MAP_STATE.x, y - MAP_STATE.y, z - MAP_STATE.z);
}