package net.fabricmc.fabric.impl.renderer;

import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.renderer.v1.mesh.QuadView;
import net.fabricmc.fabric.api.renderer.v1.model.SpriteFinder;
import net.minecraft.class_1058;
import net.minecraft.class_2960;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Environment(EnvType.CLIENT)
public class SpriteFinderImpl implements SpriteFinder {
   private static final Logger LOGGER = LoggerFactory.getLogger(SpriteFinderImpl.class);
   private final Node root = new Node(0.5F, 0.5F, 0.25F);
   private final class_1058 missingSprite;
   private int badSpriteCount = 0;

   public SpriteFinderImpl(Map<class_2960, class_1058> sprites, class_1058 missingSprite) {
      this.missingSprite = missingSprite;
      Collection var10000 = sprites.values();
      Node var10001 = this.root;
      Objects.requireNonNull(var10001);
      var10000.forEach(var10001::add);
   }

   public class_1058 find(QuadView quad) {
      float u = 0.0F;
      float v = 0.0F;

      for(int i = 0; i < 4; ++i) {
         u += quad.u(i);
         v += quad.v(i);
      }

      return this.find(u * 0.25F, v * 0.25F);
   }

   public class_1058 find(float u, float v) {
      return this.root.find(u, v);
   }

   @Environment(EnvType.CLIENT)
   private class Node {
      final float midU;
      final float midV;
      final float cellRadius;
      @Nullable Object lowLow = null;
      @Nullable Object lowHigh = null;
      @Nullable Object highLow = null;
      @Nullable Object highHigh = null;
      static final float EPS = 1.0E-5F;

      Node(float midU, float midV, float radius) {
         this.midU = midU;
         this.midV = midV;
         this.cellRadius = radius;
      }

      void add(class_1058 sprite) {
         if (!(sprite.method_4594() < -1.0E-5F) && !(sprite.method_4577() > 1.00001F) && !(sprite.method_4593() < -1.0E-5F) && !(sprite.method_4575() > 1.00001F)) {
            boolean lowU = sprite.method_4594() < this.midU - 1.0E-5F;
            boolean highU = sprite.method_4577() > this.midU + 1.0E-5F;
            boolean lowV = sprite.method_4593() < this.midV - 1.0E-5F;
            boolean highV = sprite.method_4575() > this.midV + 1.0E-5F;
            if (lowU && lowV) {
               this.lowLow = this.addInner(sprite, this.lowLow, -1, -1);
            }

            if (lowU && highV) {
               this.lowHigh = this.addInner(sprite, this.lowHigh, -1, 1);
            }

            if (highU && lowV) {
               this.highLow = this.addInner(sprite, this.highLow, 1, -1);
            }

            if (highU && highV) {
               this.highHigh = this.addInner(sprite, this.highHigh, 1, 1);
            }

         } else {
            if (SpriteFinderImpl.this.badSpriteCount++ < 5) {
               String errorMessage = "SpriteFinderImpl: Skipping sprite {} with broken bounds [{}, {}]x[{}, {}]. Sprite bounds should be between 0 and 1.";
               SpriteFinderImpl.LOGGER.error(errorMessage, new Object[]{sprite.method_45851().method_45816(), sprite.method_4594(), sprite.method_4577(), sprite.method_4593(), sprite.method_4575()});
            }

         }
      }

      private Object addInner(class_1058 sprite, @Nullable Object quadrant, int uStep, int vStep) {
         if (quadrant == null) {
            return sprite;
         } else if (quadrant instanceof Node) {
            Node node = (Node)quadrant;
            node.add(sprite);
            return quadrant;
         } else {
            Node n = SpriteFinderImpl.this.new Node(this.midU + this.cellRadius * (float)uStep, this.midV + this.cellRadius * (float)vStep, this.cellRadius * 0.5F);
            if (quadrant instanceof class_1058) {
               class_1058 prevSprite = (class_1058)quadrant;
               n.add(prevSprite);
            }

            n.add(sprite);
            return n;
         }
      }

      private class_1058 find(float u, float v) {
         if (u < this.midU) {
            return v < this.midV ? this.findInner(this.lowLow, u, v) : this.findInner(this.lowHigh, u, v);
         } else {
            return v < this.midV ? this.findInner(this.highLow, u, v) : this.findInner(this.highHigh, u, v);
         }
      }

      private class_1058 findInner(@Nullable Object quadrant, float u, float v) {
         if (quadrant instanceof Node node) {
            return node.find(u, v);
         } else if (quadrant instanceof class_1058 sprite) {
            return sprite;
         } else {
            return SpriteFinderImpl.this.missingSprite;
         }
      }
   }
}
