use gdnative::prelude::*;
mod chunk;

// ```
// 0     1
// +-----+
// |   / |
// |  /  |
// | /   |
// +-----+
// 3     2
// ```
const FACES: [[usize; 4]; 6] = [
  [5, 4, 0, 1],
  [7, 6, 2, 3],
  [6, 5, 1, 2],
  [4, 7, 3, 0],
  [6, 7, 4, 5],
  [1, 0, 3, 2],
];

const FACE_ORDER: [usize; 6] = [2, 3, 1, 1, 3, 0];

const VERTICES: [[f32; 3]; 8] = [
  [0., 0., 0.],
  [1., 0., 0.],
  [1., 0., 1.],
  [0., 0., 1.],
  [0., 1., 0.],
  [1., 1., 0.],
  [1., 1., 1.],
  [0., 1., 1.],
];

const NORMALS: [[f32; 3]; 6] = [
  [0., 0., -1.],
  [0., 0., 1.],
  [1., 0., 0.],
  [-1., 0., 0.],
  [0., 1., 0.],
  [0., -1., 0.],
];

const UVS: [[f32; 2]; 4] = [
  [0., 0.],
  [1., 0.],
  [1., 1.],
  [0., 1.]
];

#[derive(Clone)]
struct MeshData {
  voxels: i32,
  vertices: TypedArray<Vector3>,
  uvs: TypedArray<Vector2>,
  normals: TypedArray<Vector3>
}

impl MeshData {
  fn new() -> MeshData {
    MeshData {
      voxels: 0,
      vertices: TypedArray::<Vector3>::new(),
      uvs: TypedArray::<Vector2>::new(),
      normals: TypedArray::<Vector3>::new()
    }
  }

  fn resize (&mut self) {
    self.vertices.resize(self.voxels * 36);
    self.normals.resize(self.voxels * 36);
    self.uvs.resize(self.voxels * 36);
  }

  fn to_dict (self) -> Dictionary<Unique> {
    let dict = Dictionary::<Unique>::new();
    dict.insert("vertices", self.vertices);
    dict.insert("uvs", self.uvs);
    dict.insert("normals", self.normals);
    dict
  }
}

struct SurfaceData {
  surfaces: Vec<MeshData>
}

impl SurfaceData {
  fn new(size: i32) -> SurfaceData {
    let mut surfaces = Vec::<MeshData>::new();
    surfaces.resize(size as usize, MeshData::new());

    SurfaceData {
      surfaces
    }
  }

  fn get(&mut self, idx: usize) -> &mut MeshData {
    &mut self.surfaces[idx]
  }

  fn to_array(self) -> VariantArray<Unique> {
    let array = VariantArray::<Unique>::new();
    for mesh_data in self.surfaces.into_iter() {
      array.push(mesh_data.to_dict());
    }

    array
  }
}

#[derive(NativeClass)]
#[inherit(Node)]
struct VoxelMesher;

#[gdnative::methods]
impl VoxelMesher {
  fn new(_owner: &Node) -> Self {
    VoxelMesher
  }

  #[export]
  fn mesh(
    &self,
    _owner: &Node,
    voxel_name_index: TypedArray<GodotString>,
    chunk_dimensions: Vector3,
    chunk_data: TypedArray<i32>
  ) -> VariantArray<Unique> {
    let mut surface_data = SurfaceData::new(voxel_name_index.len());
    let max_x = chunk_dimensions.x as i32;
    let max_y = chunk_dimensions.y as i32;

    for i in 0..chunk_data.len() {
      let voxel_id = chunk_data.get(i);
      if voxel_id == 0 { continue; }
      let mut mesh_data = surface_data.get(voxel_id as usize);
      mesh_data.voxels += 1;
    }

    for mesh_data in &mut surface_data.surfaces {
      mesh_data.resize();
      mesh_data.voxels = 0;
    }

    for i in 0..chunk_data.len() {
      let voxel_id = chunk_data.get(i);
      if voxel_id == 0 {
        continue;
      }
      let mut mesh_data = surface_data.get(voxel_id as usize);

      let z = i / (max_x * max_y);
      let i2 = i - (z * max_x * max_y);
      let y = i2 / max_x;
      let x = i2 % max_x;

      let pos: [i32; 3] = [x, y, z];

      self.mesh_voxel(&mut mesh_data, pos);
      mesh_data.voxels += 1;
    }

    surface_data.to_array()
  }

  fn mesh_voxel(
    &self,
    mesh_data: &mut MeshData,
    position: [i32; 3]
  ) {
    let voxel_offset = mesh_data.voxels * 36 as i32;

    for i in 0..36 {
      let idx = voxel_offset + i as i32;
      let idx_vertex = i % 6;
      let idx_face = (i - idx_vertex) / 6;
      let idx_side = FACES[idx_face];
      let idx_vertex_coords = FACE_ORDER[idx_vertex];

      let mut vertices = VERTICES[idx_side[idx_vertex_coords]];
      for j in 0..3 { vertices[j] += position[j] as f32; }

      let normals = NORMALS[idx_face];
      let uvs = UVS[idx_vertex_coords];

      mesh_data.vertices.set(idx, Vector3::new(vertices[0], vertices[1], vertices[2]));
      mesh_data.normals.set(idx, Vector3::new(normals[0], normals[1], normals[2]));
      mesh_data.uvs.set(idx, Vector2::new(uvs[0], uvs[1]))
    }
  }
}

fn init(handle: InitHandle) {
  handle.add_class::<VoxelMesher>();
}

godot_init!(init);
