package net.vulkanmod.render.chunk.build.frapi.mesh;

import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
import net.fabricmc.fabric.api.renderer.v1.mesh.ShadeMode;
import net.fabricmc.fabric.api.util.TriState;
import net.minecraft.class_10444;
import net.minecraft.class_11515;
import net.minecraft.class_2350;
import net.vulkanmod.render.chunk.build.frapi.helper.ColorHelper;
import net.vulkanmod.render.chunk.build.frapi.helper.GeometryHelper;
import net.vulkanmod.render.chunk.build.frapi.helper.NormalHelper;
import net.vulkanmod.render.chunk.cull.QuadFacing;
import net.vulkanmod.render.model.quad.ModelQuadFlags;
import net.vulkanmod.render.model.quad.ModelQuadView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector2f;
import org.joml.Vector3f;

public class QuadViewImpl implements QuadView, ModelQuadView {
   protected @Nullable class_2350 nominalFace;
   protected boolean isGeometryInvalid = true;
   protected final Vector3f faceNormal = new Vector3f();
   protected int[] data;
   protected int baseIndex = 0;
   protected QuadFacing facing;

   public void load() {
      this.isGeometryInvalid = false;
      this.nominalFace = this.lightFace();
      NormalHelper.unpackNormal(this.packedFaceNormal(), this.faceNormal);
      this.facing = QuadFacing.fromNormal(this.faceNormal);
   }

   protected void computeGeometry() {
      if (this.isGeometryInvalid) {
         this.isGeometryInvalid = false;
         NormalHelper.computeFaceNormal(this.faceNormal, this);
         this.data[this.baseIndex + 1] = NormalHelper.packNormal(this.faceNormal);
         class_2350 lightFace = GeometryHelper.lightFace(this);
         this.data[this.baseIndex + 0] = EncodingFormat.lightFace(this.data[this.baseIndex + 0], lightFace);
         this.data[this.baseIndex + 0] = EncodingFormat.geometryFlags(this.data[this.baseIndex + 0], ModelQuadFlags.getQuadFlags(this, lightFace));
         this.facing = QuadFacing.fromNormal(this.faceNormal);
      }

   }

   public int geometryFlags() {
      this.computeGeometry();
      return EncodingFormat.geometryFlags(this.data[this.baseIndex + 0]);
   }

   public float x(int vertexIndex) {
      return Float.intBitsToFloat(this.data[this.baseIndex + vertexIndex * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_X]);
   }

   public float y(int vertexIndex) {
      return Float.intBitsToFloat(this.data[this.baseIndex + vertexIndex * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_Y]);
   }

   public float z(int vertexIndex) {
      return Float.intBitsToFloat(this.data[this.baseIndex + vertexIndex * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_Z]);
   }

   public float posByIndex(int vertexIndex, int coordinateIndex) {
      return Float.intBitsToFloat(this.data[this.baseIndex + vertexIndex * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_X + coordinateIndex]);
   }

   public Vector3f copyPos(int vertexIndex, @Nullable Vector3f target) {
      if (target == null) {
         target = new Vector3f();
      }

      int index = this.baseIndex + vertexIndex * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_X;
      target.set(Float.intBitsToFloat(this.data[index]), Float.intBitsToFloat(this.data[index + 1]), Float.intBitsToFloat(this.data[index + 2]));
      return target;
   }

   public int color(int vertexIndex) {
      return this.data[this.baseIndex + vertexIndex * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_COLOR];
   }

   public float u(int vertexIndex) {
      return Float.intBitsToFloat(this.data[this.baseIndex + vertexIndex * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_U]);
   }

   public float v(int vertexIndex) {
      return Float.intBitsToFloat(this.data[this.baseIndex + vertexIndex * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_V]);
   }

   public Vector2f copyUv(int vertexIndex, @Nullable Vector2f target) {
      if (target == null) {
         target = new Vector2f();
      }

      int index = this.baseIndex + vertexIndex * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_U;
      target.set(Float.intBitsToFloat(this.data[index]), Float.intBitsToFloat(this.data[index + 1]));
      return target;
   }

   public int lightmap(int vertexIndex) {
      return this.data[this.baseIndex + vertexIndex * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_LIGHTMAP];
   }

   public final int normalFlags() {
      return EncodingFormat.normalFlags(this.data[this.baseIndex + 0]);
   }

   public final boolean hasNormal(int vertexIndex) {
      return (this.normalFlags() & 1 << vertexIndex) != 0;
   }

   public final boolean hasVertexNormals() {
      return this.normalFlags() != 0;
   }

   public final boolean hasAllVertexNormals() {
      return (this.normalFlags() & 15) == 15;
   }

   protected final int normalIndex(int vertexIndex) {
      return this.baseIndex + vertexIndex * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_NORMAL;
   }

   public final float normalX(int vertexIndex) {
      return this.hasNormal(vertexIndex) ? NormalHelper.unpackNormalX(this.data[this.normalIndex(vertexIndex)]) : Float.NaN;
   }

   public final float normalY(int vertexIndex) {
      return this.hasNormal(vertexIndex) ? NormalHelper.unpackNormalY(this.data[this.normalIndex(vertexIndex)]) : Float.NaN;
   }

   public final float normalZ(int vertexIndex) {
      return this.hasNormal(vertexIndex) ? NormalHelper.unpackNormalZ(this.data[this.normalIndex(vertexIndex)]) : Float.NaN;
   }

   public final @Nullable Vector3f copyNormal(int vertexIndex, @Nullable Vector3f target) {
      if (this.hasNormal(vertexIndex)) {
         if (target == null) {
            target = new Vector3f();
         }

         int normal = this.data[this.normalIndex(vertexIndex)];
         NormalHelper.unpackNormal(normal, target);
         return target;
      } else {
         return null;
      }
   }

   public final @NotNull class_2350 lightFace() {
      this.computeGeometry();
      return EncodingFormat.lightFace(this.data[this.baseIndex + 0]);
   }

   public final @Nullable class_2350 nominalFace() {
      return this.nominalFace;
   }

   public final int packedFaceNormal() {
      this.computeGeometry();
      return this.data[this.baseIndex + 1];
   }

   public final Vector3f faceNormal() {
      this.computeGeometry();
      return this.faceNormal;
   }

   public final @Nullable class_2350 cullFace() {
      return EncodingFormat.cullFace(this.data[this.baseIndex + 0]);
   }

   public @Nullable class_11515 renderLayer() {
      return EncodingFormat.renderLayer(this.data[this.baseIndex + 0]);
   }

   public boolean emissive() {
      return EncodingFormat.emissive(this.data[this.baseIndex + 0]);
   }

   public boolean diffuseShade() {
      return EncodingFormat.diffuseShade(this.data[this.baseIndex + 0]);
   }

   public TriState ambientOcclusion() {
      return EncodingFormat.ambientOcclusion(this.data[this.baseIndex + 0]);
   }

   public class_10444.@Nullable class_10445 glint() {
      return EncodingFormat.glint(this.data[this.baseIndex + 0]);
   }

   public ShadeMode shadeMode() {
      return EncodingFormat.shadeMode(this.data[this.baseIndex + 0]);
   }

   public final int tintIndex() {
      return this.data[this.baseIndex + 2];
   }

   public final int tag() {
      return this.data[this.baseIndex + 3];
   }

   public final void toVanilla(int[] target, int targetIndex) {
      System.arraycopy(this.data, this.baseIndex + 4, target, targetIndex, EncodingFormat.QUAD_STRIDE);
      int colorIndex = targetIndex + EncodingFormat.VERTEX_COLOR - 4;

      for(int i = 0; i < 4; ++i) {
         target[colorIndex] = ColorHelper.toVanillaColor(target[colorIndex]);
         colorIndex += VANILLA_VERTEX_STRIDE;
      }

   }

   public int getFlags() {
      return this.geometryFlags();
   }

   public float getX(int idx) {
      return this.x(idx);
   }

   public float getY(int idx) {
      return this.y(idx);
   }

   public float getZ(int idx) {
      return this.z(idx);
   }

   public int getColor(int idx) {
      return this.color(idx);
   }

   public float getU(int idx) {
      return this.u(idx);
   }

   public float getV(int idx) {
      return this.v(idx);
   }

   public int getColorIndex() {
      return this.tintIndex();
   }

   public class_2350 getFacingDirection() {
      return this.lightFace();
   }

   public int getNormal() {
      return this.packedFaceNormal();
   }

   public QuadFacing getQuadFacing() {
      return this.facing;
   }
}
