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

import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Objects;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadTransform;
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_1058;
import net.minecraft.class_11515;
import net.minecraft.class_2350;
import net.minecraft.class_765;
import net.minecraft.class_777;
import net.vulkanmod.render.chunk.build.frapi.helper.ColorHelper;
import net.vulkanmod.render.chunk.build.frapi.helper.NormalHelper;
import net.vulkanmod.render.chunk.build.frapi.helper.TextureHelper;
import net.vulkanmod.render.model.quad.ModelQuadView;
import org.jetbrains.annotations.Nullable;

public abstract class MutableQuadViewImpl extends QuadViewImpl implements QuadEmitter {
   private static final QuadTransform NO_TRANSFORM = (q) -> true;
   private static final int[] DEFAULT_QUAD_DATA;
   protected boolean hasTransform = false;
   private QuadTransform activeTransform;
   private final ObjectArrayList<QuadTransform> transformStack;
   private final QuadTransform stackTransform;

   public MutableQuadViewImpl() {
      this.activeTransform = NO_TRANSFORM;
      this.transformStack = new ObjectArrayList();
      this.stackTransform = (q) -> {
         int i = this.transformStack.size() - 1;

         while(i >= 0) {
            if (!((QuadTransform)this.transformStack.get(i--)).transform(q)) {
               return false;
            }
         }

         return true;
      };
   }

   public final void clear() {
      System.arraycopy(DEFAULT_QUAD_DATA, 0, this.data, this.baseIndex, EncodingFormat.TOTAL_STRIDE);
      this.isGeometryInvalid = true;
      this.nominalFace = null;
   }

   public MutableQuadViewImpl pos(int vertexIndex, float x, float y, float z) {
      int index = this.baseIndex + vertexIndex * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_X;
      this.data[index] = Float.floatToRawIntBits(x);
      this.data[index + 1] = Float.floatToRawIntBits(y);
      this.data[index + 2] = Float.floatToRawIntBits(z);
      this.isGeometryInvalid = true;
      return this;
   }

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

   public MutableQuadViewImpl uv(int vertexIndex, float u, float v) {
      int i = this.baseIndex + vertexIndex * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_U;
      this.data[i] = Float.floatToRawIntBits(u);
      this.data[i + 1] = Float.floatToRawIntBits(v);
      return this;
   }

   public MutableQuadViewImpl spriteBake(class_1058 sprite, int bakeFlags) {
      TextureHelper.bakeSprite(this, sprite, bakeFlags);
      return this;
   }

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

   protected void normalFlags(int flags) {
      this.data[this.baseIndex + 0] = EncodingFormat.normalFlags(this.data[this.baseIndex + 0], flags);
   }

   public MutableQuadViewImpl normal(int vertexIndex, float x, float y, float z) {
      this.normalFlags(this.normalFlags() | 1 << vertexIndex);
      this.data[this.baseIndex + vertexIndex * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_NORMAL] = NormalHelper.packNormal(x, y, z);
      return this;
   }

   public final void populateMissingNormals() {
      int normalFlags = this.normalFlags();
      if (normalFlags != 15) {
         int packedFaceNormal = this.packedFaceNormal();

         for(int v = 0; v < 4; ++v) {
            if ((normalFlags & 1 << v) == 0) {
               this.data[this.baseIndex + v * EncodingFormat.VERTEX_STRIDE + EncodingFormat.VERTEX_NORMAL] = packedFaceNormal;
            }
         }

         this.normalFlags(15);
      }
   }

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

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

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

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

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

   public MutableQuadViewImpl ambientOcclusion(TriState ao) {
      Objects.requireNonNull(ao, "ambient occlusion TriState may not be null");
      this.data[this.baseIndex + 0] = EncodingFormat.ambientOcclusion(this.data[this.baseIndex + 0], ao);
      return this;
   }

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

   public MutableQuadViewImpl shadeMode(ShadeMode mode) {
      Objects.requireNonNull(mode, "ShadeMode may not be null");
      this.data[this.baseIndex + 0] = EncodingFormat.shadeMode(this.data[this.baseIndex + 0], mode);
      return this;
   }

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

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

   public MutableQuadViewImpl copyFrom(QuadView quad) {
      QuadViewImpl q = (QuadViewImpl)quad;
      System.arraycopy(q.data, q.baseIndex, this.data, this.baseIndex, EncodingFormat.TOTAL_STRIDE);
      this.nominalFace = q.nominalFace;
      this.isGeometryInvalid = q.isGeometryInvalid;
      if (!this.isGeometryInvalid) {
         this.faceNormal.set(q.faceNormal);
      }

      return this;
   }

   public final MutableQuadViewImpl fromVanilla(int[] quadData, int startIndex) {
      System.arraycopy(quadData, startIndex, this.data, this.baseIndex + 4, VANILLA_QUAD_STRIDE);
      this.isGeometryInvalid = true;
      int normalFlags = 0;
      int colorIndex = this.baseIndex + EncodingFormat.VERTEX_COLOR;
      int normalIndex = this.baseIndex + EncodingFormat.VERTEX_NORMAL;

      for(int i = 0; i < 4; ++i) {
         this.data[colorIndex] = ColorHelper.fromVanillaColor(this.data[colorIndex]);
         if ((this.data[normalIndex] & 16777215) != 0) {
            normalFlags |= 1 << i;
         }

         colorIndex += EncodingFormat.VERTEX_STRIDE;
         normalIndex += EncodingFormat.VERTEX_STRIDE;
      }

      this.normalFlags(normalFlags);
      return this;
   }

   public final MutableQuadViewImpl fromBakedQuad(class_777 quad) {
      this.fromVanilla(quad.comp_3721(), 0);
      this.nominalFace(quad.comp_3723());
      this.diffuseShade(quad.comp_3725());
      this.tintIndex(quad.comp_3722());
      ModelQuadView quadView = (ModelQuadView)quad;
      int normal = quadView.getNormal();
      this.data[this.baseIndex + 1] = normal;
      NormalHelper.unpackNormalTo(normal, this.faceNormal);
      class_2350 lightFace = quadView.lightFace();
      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], quadView.getFlags());
      this.facing = quadView.getQuadFacing();
      this.isGeometryInvalid = false;
      int lightEmission = quad.comp_3726();
      if (lightEmission > 0) {
         for(int i = 0; i < 4; ++i) {
            this.lightmap(i, class_765.method_62228(this.lightmap(i), lightEmission));
         }
      }

      return this;
   }

   public void pushTransform(QuadTransform transform) {
      if (transform == null) {
         throw new NullPointerException("QuadTransform cannot be null!");
      } else {
         this.transformStack.push(transform);
         this.hasTransform = true;
         if (this.transformStack.size() == 1) {
            this.activeTransform = transform;
         } else if (this.transformStack.size() == 2) {
            this.activeTransform = this.stackTransform;
         }

      }
   }

   public void popTransform() {
      this.transformStack.pop();
      if (this.transformStack.size() == 0) {
         this.activeTransform = NO_TRANSFORM;
         this.hasTransform = false;
      } else if (this.transformStack.size() == 1) {
         this.activeTransform = (QuadTransform)this.transformStack.get(0);
      }

   }

   protected abstract void emitDirectly();

   public final void transformAndEmit() {
      if (this.activeTransform.transform(this)) {
         this.emitDirectly();
      }

   }

   public final MutableQuadViewImpl emit() {
      this.transformAndEmit();
      this.clear();
      return this;
   }

   static {
      DEFAULT_QUAD_DATA = new int[EncodingFormat.TOTAL_STRIDE];
      MutableQuadViewImpl quad = new MutableQuadViewImpl() {
         protected void emitDirectly() {
         }
      };
      quad.data = DEFAULT_QUAD_DATA;
      quad.color(-1, -1, -1, -1);
      quad.cullFace((class_2350)null);
      quad.renderLayer((class_11515)null);
      quad.diffuseShade(true);
      quad.ambientOcclusion(TriState.DEFAULT);
      quad.glint((class_10444.class_10445)null);
      quad.tintIndex(-1);
      quad.tintIndex(-1);
   }
}
