package net.minecraft.world.level.lighting;

import java.util.Objects;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.DataLayer;
import net.minecraft.world.level.chunk.LightChunk;
import net.minecraft.world.level.chunk.LightChunkGetter;
import org.jetbrains.annotations.VisibleForTesting;

public final class SkyLightEngine extends LightEngine<SkyLightSectionStorage.SkyDataLayerStorageMap, SkyLightSectionStorage> {
   private static final long REMOVE_TOP_SKY_SOURCE_ENTRY = LightEngine.QueueEntry.decreaseAllDirections(15);
   private static final long REMOVE_SKY_SOURCE_ENTRY = LightEngine.QueueEntry.decreaseSkipOneDirection(15, Direction.UP);
   private static final long ADD_SKY_SOURCE_ENTRY = LightEngine.QueueEntry.increaseSkipOneDirection(15, false, Direction.UP);
   private final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
   private final ChunkSkyLightSources emptyChunkSources;

   public SkyLightEngine(LightChunkGetter p_75843_) {
      this(p_75843_, new SkyLightSectionStorage(p_75843_));
   }

   @VisibleForTesting
   protected SkyLightEngine(LightChunkGetter p_282215_, SkyLightSectionStorage p_282341_) {
      super(p_282215_, p_282341_);
      this.emptyChunkSources = new ChunkSkyLightSources(p_282215_.getLevel());
   }

   private static boolean isSourceLevel(int p_285004_) {
      return p_285004_ == 15;
   }

   private int getLowestSourceY(int p_285058_, int p_285191_, int p_285111_) {
      ChunkSkyLightSources chunkskylightsources = this.getChunkSources(SectionPos.blockToSectionCoord(p_285058_), SectionPos.blockToSectionCoord(p_285191_));
      return chunkskylightsources == null ? p_285111_ : chunkskylightsources.getLowestSourceY(SectionPos.sectionRelative(p_285058_), SectionPos.sectionRelative(p_285191_));
   }

   @Nullable
   private ChunkSkyLightSources getChunkSources(int p_285270_, int p_285307_) {
      LightChunk lightchunk = this.chunkSource.getChunkForLighting(p_285270_, p_285307_);
      return lightchunk != null ? lightchunk.getSkyLightSources() : null;
   }

   protected void checkNode(long p_75859_) {
      int i = BlockPos.getX(p_75859_);
      int j = BlockPos.getY(p_75859_);
      int k = BlockPos.getZ(p_75859_);
      long l = SectionPos.blockToSection(p_75859_);
      int i1 = this.storage.lightOnInSection(l) ? this.getLowestSourceY(i, k, Integer.MAX_VALUE) : Integer.MAX_VALUE;
      if (i1 != Integer.MAX_VALUE) {
         this.updateSourcesInColumn(i, k, i1);
      }

      if (this.storage.storingLightForSection(l)) {
         boolean flag = j >= i1;
         if (flag) {
            this.enqueueDecrease(p_75859_, REMOVE_SKY_SOURCE_ENTRY);
            this.enqueueIncrease(p_75859_, ADD_SKY_SOURCE_ENTRY);
         } else {
            int j1 = this.storage.getStoredLevel(p_75859_);
            if (j1 > 0) {
               this.storage.setStoredLevel(p_75859_, 0);
               this.enqueueDecrease(p_75859_, LightEngine.QueueEntry.decreaseAllDirections(j1));
            } else {
               this.enqueueDecrease(p_75859_, PULL_LIGHT_IN_ENTRY);
            }
         }

      }
   }

   private void updateSourcesInColumn(int p_285053_, int p_285140_, int p_285337_) {
      int i = SectionPos.sectionToBlockCoord(this.storage.getBottomSectionY());
      this.removeSourcesBelow(p_285053_, p_285140_, p_285337_, i);
      this.addSourcesAbove(p_285053_, p_285140_, p_285337_, i);
   }

   private void removeSourcesBelow(int p_285475_, int p_285138_, int p_285130_, int p_285112_) {
      if (p_285130_ > p_285112_) {
         int i = SectionPos.blockToSectionCoord(p_285475_);
         int j = SectionPos.blockToSectionCoord(p_285138_);
         int k = p_285130_ - 1;

         for(int l = SectionPos.blockToSectionCoord(k); this.storage.hasLightDataAtOrBelow(l); --l) {
            if (this.storage.storingLightForSection(SectionPos.asLong(i, l, j))) {
               int i1 = SectionPos.sectionToBlockCoord(l);
               int j1 = i1 + 15;

               for(int k1 = Math.min(j1, k); k1 >= i1; --k1) {
                  long l1 = BlockPos.asLong(p_285475_, k1, p_285138_);
                  if (!isSourceLevel(this.storage.getStoredLevel(l1))) {
                     return;
                  }

                  this.storage.setStoredLevel(l1, 0);
                  this.enqueueDecrease(l1, k1 == p_285130_ - 1 ? REMOVE_TOP_SKY_SOURCE_ENTRY : REMOVE_SKY_SOURCE_ENTRY);
               }
            }
         }

      }
   }

   private void addSourcesAbove(int p_285241_, int p_285212_, int p_284972_, int p_285134_) {
      int i = SectionPos.blockToSectionCoord(p_285241_);
      int j = SectionPos.blockToSectionCoord(p_285212_);
      int k = Math.max(Math.max(this.getLowestSourceY(p_285241_ - 1, p_285212_, Integer.MIN_VALUE), this.getLowestSourceY(p_285241_ + 1, p_285212_, Integer.MIN_VALUE)), Math.max(this.getLowestSourceY(p_285241_, p_285212_ - 1, Integer.MIN_VALUE), this.getLowestSourceY(p_285241_, p_285212_ + 1, Integer.MIN_VALUE)));
      int l = Math.max(p_284972_, p_285134_);

      for(long i1 = SectionPos.asLong(i, SectionPos.blockToSectionCoord(l), j); !this.storage.isAboveData(i1); i1 = SectionPos.offset(i1, Direction.UP)) {
         if (this.storage.storingLightForSection(i1)) {
            int j1 = SectionPos.sectionToBlockCoord(SectionPos.y(i1));
            int k1 = j1 + 15;

            for(int l1 = Math.max(j1, l); l1 <= k1; ++l1) {
               long i2 = BlockPos.asLong(p_285241_, l1, p_285212_);
               if (isSourceLevel(this.storage.getStoredLevel(i2))) {
                  return;
               }

               this.storage.setStoredLevel(i2, 15);
               if (l1 < k || l1 == p_284972_) {
                  this.enqueueIncrease(i2, ADD_SKY_SOURCE_ENTRY);
               }
            }
         }
      }

   }

   protected void propagateIncrease(long p_285341_, long p_285204_, int p_285003_) {
      BlockState blockstate = null;
      int i = this.countEmptySectionsBelowIfAtBorder(p_285341_);

      for(Direction direction : PROPAGATION_DIRECTIONS) {
         if (LightEngine.QueueEntry.shouldPropagateInDirection(p_285204_, direction)) {
            long j = BlockPos.offset(p_285341_, direction);
            if (this.storage.storingLightForSection(SectionPos.blockToSection(j))) {
               int k = this.storage.getStoredLevel(j);
               int l = p_285003_ - 1;
               if (l > k) {
                  this.mutablePos.set(j);
                  BlockState blockstate1 = this.getState(this.mutablePos);
                  int i1 = p_285003_ - this.getOpacity(blockstate1, this.mutablePos);
                  if (i1 > k) {
                     if (blockstate == null) {
                        blockstate = LightEngine.QueueEntry.isFromEmptyShape(p_285204_) ? Blocks.AIR.defaultBlockState() : this.getState(this.mutablePos.set(p_285341_));
                     }

                     if (!this.shapeOccludes(p_285341_, blockstate, j, blockstate1, direction)) {
                        this.storage.setStoredLevel(j, i1);
                        if (i1 > 1) {
                           this.enqueueIncrease(j, LightEngine.QueueEntry.increaseSkipOneDirection(i1, isEmptyShape(blockstate1), direction.getOpposite()));
                        }

                        this.propagateFromEmptySections(j, direction, i1, true, i);
                     }
                  }
               }
            }
         }
      }

   }

   protected void propagateDecrease(long p_285015_, long p_285395_) {
      int i = this.countEmptySectionsBelowIfAtBorder(p_285015_);
      int j = LightEngine.QueueEntry.getFromLevel(p_285395_);

      for(Direction direction : PROPAGATION_DIRECTIONS) {
         if (LightEngine.QueueEntry.shouldPropagateInDirection(p_285395_, direction)) {
            long k = BlockPos.offset(p_285015_, direction);
            if (this.storage.storingLightForSection(SectionPos.blockToSection(k))) {
               int l = this.storage.getStoredLevel(k);
               if (l != 0) {
                  if (l <= j - 1) {
                     this.storage.setStoredLevel(k, 0);
                     this.enqueueDecrease(k, LightEngine.QueueEntry.decreaseSkipOneDirection(l, direction.getOpposite()));
                     this.propagateFromEmptySections(k, direction, l, false, i);
                  } else {
                     this.enqueueIncrease(k, LightEngine.QueueEntry.increaseOnlyOneDirection(l, false, direction.getOpposite()));
                  }
               }
            }
         }
      }

   }

   private int countEmptySectionsBelowIfAtBorder(long p_285356_) {
      int i = BlockPos.getY(p_285356_);
      int j = SectionPos.sectionRelative(i);
      if (j != 0) {
         return 0;
      } else {
         int k = BlockPos.getX(p_285356_);
         int l = BlockPos.getZ(p_285356_);
         int i1 = SectionPos.sectionRelative(k);
         int j1 = SectionPos.sectionRelative(l);
         if (i1 != 0 && i1 != 15 && j1 != 0 && j1 != 15) {
            return 0;
         } else {
            int k1 = SectionPos.blockToSectionCoord(k);
            int l1 = SectionPos.blockToSectionCoord(i);
            int i2 = SectionPos.blockToSectionCoord(l);

            int j2;
            for(j2 = 0; !this.storage.storingLightForSection(SectionPos.asLong(k1, l1 - j2 - 1, i2)) && this.storage.hasLightDataAtOrBelow(l1 - j2 - 1); ++j2) {
            }

            return j2;
         }
      }
   }

   private void propagateFromEmptySections(long p_284965_, Direction p_285308_, int p_284977_, boolean p_285001_, int p_285052_) {
      if (p_285052_ != 0) {
         int i = BlockPos.getX(p_284965_);
         int j = BlockPos.getZ(p_284965_);
         if (crossedSectionEdge(p_285308_, SectionPos.sectionRelative(i), SectionPos.sectionRelative(j))) {
            int k = BlockPos.getY(p_284965_);
            int l = SectionPos.blockToSectionCoord(i);
            int i1 = SectionPos.blockToSectionCoord(j);
            int j1 = SectionPos.blockToSectionCoord(k) - 1;
            int k1 = j1 - p_285052_ + 1;

            while(j1 >= k1) {
               if (!this.storage.storingLightForSection(SectionPos.asLong(l, j1, i1))) {
                  --j1;
               } else {
                  int l1 = SectionPos.sectionToBlockCoord(j1);

                  for(int i2 = 15; i2 >= 0; --i2) {
                     long j2 = BlockPos.asLong(i, l1 + i2, j);
                     if (p_285001_) {
                        this.storage.setStoredLevel(j2, p_284977_);
                        if (p_284977_ > 1) {
                           this.enqueueIncrease(j2, LightEngine.QueueEntry.increaseSkipOneDirection(p_284977_, true, p_285308_.getOpposite()));
                        }
                     } else {
                        this.storage.setStoredLevel(j2, 0);
                        this.enqueueDecrease(j2, LightEngine.QueueEntry.decreaseSkipOneDirection(p_284977_, p_285308_.getOpposite()));
                     }
                  }

                  --j1;
               }
            }

         }
      }
   }

   private static boolean crossedSectionEdge(Direction p_285014_, int p_284991_, int p_285468_) {
      boolean flag;
      switch (p_285014_) {
         case NORTH:
            flag = p_285468_ == 15;
            break;
         case SOUTH:
            flag = p_285468_ == 0;
            break;
         case WEST:
            flag = p_284991_ == 15;
            break;
         case EAST:
            flag = p_284991_ == 0;
            break;
         default:
            flag = false;
      }

      return flag;
   }

   public void setLightEnabled(ChunkPos p_285459_, boolean p_285013_) {
      super.setLightEnabled(p_285459_, p_285013_);
      if (p_285013_) {
         ChunkSkyLightSources chunkskylightsources = Objects.requireNonNullElse(this.getChunkSources(p_285459_.x, p_285459_.z), this.emptyChunkSources);
         int i = chunkskylightsources.getHighestLowestSourceY() - 1;
         int j = SectionPos.blockToSectionCoord(i) + 1;
         long k = SectionPos.getZeroNode(p_285459_.x, p_285459_.z);
         int l = this.storage.getTopSectionY(k);
         int i1 = Math.max(this.storage.getBottomSectionY(), j);

         for(int j1 = l - 1; j1 >= i1; --j1) {
            DataLayer datalayer = this.storage.getDataLayerToWrite(SectionPos.asLong(p_285459_.x, j1, p_285459_.z));
            if (datalayer != null && datalayer.isEmpty()) {
               datalayer.fill(15);
            }
         }
      }

   }

   public void propagateLightSources(ChunkPos p_285333_) {
      long i = SectionPos.getZeroNode(p_285333_.x, p_285333_.z);
      this.storage.setLightEnabled(i, true);
      ChunkSkyLightSources chunkskylightsources = Objects.requireNonNullElse(this.getChunkSources(p_285333_.x, p_285333_.z), this.emptyChunkSources);
      ChunkSkyLightSources chunkskylightsources1 = Objects.requireNonNullElse(this.getChunkSources(p_285333_.x, p_285333_.z - 1), this.emptyChunkSources);
      ChunkSkyLightSources chunkskylightsources2 = Objects.requireNonNullElse(this.getChunkSources(p_285333_.x, p_285333_.z + 1), this.emptyChunkSources);
      ChunkSkyLightSources chunkskylightsources3 = Objects.requireNonNullElse(this.getChunkSources(p_285333_.x - 1, p_285333_.z), this.emptyChunkSources);
      ChunkSkyLightSources chunkskylightsources4 = Objects.requireNonNullElse(this.getChunkSources(p_285333_.x + 1, p_285333_.z), this.emptyChunkSources);
      int j = this.storage.getTopSectionY(i);
      int k = this.storage.getBottomSectionY();
      int l = SectionPos.sectionToBlockCoord(p_285333_.x);
      int i1 = SectionPos.sectionToBlockCoord(p_285333_.z);

      for(int j1 = j - 1; j1 >= k; --j1) {
         long k1 = SectionPos.asLong(p_285333_.x, j1, p_285333_.z);
         DataLayer datalayer = this.storage.getDataLayerToWrite(k1);
         if (datalayer != null) {
            int l1 = SectionPos.sectionToBlockCoord(j1);
            int i2 = l1 + 15;
            boolean flag = false;

            for(int j2 = 0; j2 < 16; ++j2) {
               for(int k2 = 0; k2 < 16; ++k2) {
                  int l2 = chunkskylightsources.getLowestSourceY(k2, j2);
                  if (l2 <= i2) {
                     int i3 = j2 == 0 ? chunkskylightsources1.getLowestSourceY(k2, 15) : chunkskylightsources.getLowestSourceY(k2, j2 - 1);
                     int j3 = j2 == 15 ? chunkskylightsources2.getLowestSourceY(k2, 0) : chunkskylightsources.getLowestSourceY(k2, j2 + 1);
                     int k3 = k2 == 0 ? chunkskylightsources3.getLowestSourceY(15, j2) : chunkskylightsources.getLowestSourceY(k2 - 1, j2);
                     int l3 = k2 == 15 ? chunkskylightsources4.getLowestSourceY(0, j2) : chunkskylightsources.getLowestSourceY(k2 + 1, j2);
                     int i4 = Math.max(Math.max(i3, j3), Math.max(k3, l3));

                     for(int j4 = i2; j4 >= Math.max(l1, l2); --j4) {
                        datalayer.set(k2, SectionPos.sectionRelative(j4), j2, 15);
                        if (j4 == l2 || j4 < i4) {
                           long k4 = BlockPos.asLong(l + k2, j4, i1 + j2);
                           this.enqueueIncrease(k4, LightEngine.QueueEntry.increaseSkySourceInDirections(j4 == l2, j4 < i3, j4 < j3, j4 < k3, j4 < l3));
                        }
                     }

                     if (l2 < l1) {
                        flag = true;
                     }
                  }
               }
            }

            if (!flag) {
               break;
            }
         }
      }

   }
}