#include <limits>

#include "demo/lizard/world.hpp"
#include "assert.h"

namespace demo::world {

  struct min_max {
    int x_min;
    int x_max;
    int z_min;
    int z_max;
  };

  static platform_lookup lookup;

  static inline void clear_table()
  {
    for (int i = 0; i < lookup_table_dim * lookup_table_dim; i++) {
      lookup.table[i] = nullptr;
    }
  }

  static inline bool float_equal_int(float f)
  {
    int i = (int)f;
    float fi = (float)i;
    return fi == f;
  }

  static inline min_max calculate_platforms_area(const level& level)
  {
    float x_min = std::numeric_limits<float>::max();
    float x_max = std::numeric_limits<float>::min();

    float z_min = std::numeric_limits<float>::max();
    float z_max = std::numeric_limits<float>::min();

    for (int i = 0; i < level.platforms_length; i++) {
      const vec3& position = level.platforms[i].position;
      float scale_x_2 = level.platforms[i].scale.x * 0.5;
      float scale_z_2 = level.platforms[i].scale.z * 0.5;

      if (position.x - scale_x_2 < x_min)
        x_min = position.x - scale_x_2;
      if (position.x + scale_x_2 > x_max)
        x_max = position.x + scale_x_2;
      if (position.z - scale_z_2 < z_min)
        z_min = position.z - scale_z_2;
      if (position.z + scale_z_2 > z_max)
        z_max = position.z + scale_z_2;
    }

    assert(float_equal_int(x_min));
    assert(float_equal_int(x_max));
    assert(float_equal_int(z_min));
    assert(float_equal_int(z_max));

    return {
      (int)x_min,
      (int)x_max,
      (int)z_min,
      (int)z_max,
    };
  }

  void table_build(const level& level)
  {
    clear_table();
    min_max mm = calculate_platforms_area(level);

    lookup.xz_offset = vec2i(mm.x_min, mm.z_min);
    lookup.size = vec2i(mm.x_max - mm.x_min,
                        mm.z_max - mm.z_min);

    for (int i = 0; i < level.platforms_length; i++) {
      const vec3& position = level.platforms[i].position;
      int scale_x = (int)level.platforms[i].scale.x;
      int scale_z = (int)level.platforms[i].scale.z;
      float scale_x_2 = level.platforms[i].scale.x * 0.5;
      float scale_z_2 = level.platforms[i].scale.z * 0.5;
      level.platforms[i].touched = false;

      for (int xo = 0; xo < scale_x; xo++) {
        for (int zo = 0; zo < scale_z; zo++) {
          int x = xo + (int)(position.x - scale_x_2) - mm.x_min;
          int z = zo + (int)(position.z - scale_z_2) - mm.z_min;
          assert(x >= 0);
          assert(z >= 0);
          assert(x < lookup.size.x);
          assert(z < lookup.size.y);

          lookup.table[z * lookup.size.x + x] = &level.platforms[i];
        }
      }
    }
  }

  platform * table_lookup(float x, float z)
  {
    int xi = ((int)x) - lookup.xz_offset.x;
    int zi = ((int)z) - lookup.xz_offset.y;

    if (xi < 0 || zi < 0 || xi >= lookup.size.x || zi >= lookup.size.y) {
      return nullptr;
    }

    return lookup.table[zi * lookup.size.x + xi];
  }
}
