package org.cyclops.integrateddynamics.part.aspect;

import com.google.common.collect.Lists;
import com.google.common.math.DoubleMath;
import com.google.common.math.Stats;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundSoundPacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.StringUtil;
import net.minecraft.world.entity.EntitySelector;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.neoforged.neoforge.energy.IEnergyStorage;
import net.neoforged.neoforge.fluids.FluidStack;
import net.neoforged.neoforge.fluids.capability.IFluidHandler;
import net.neoforged.neoforge.fluids.capability.templates.EmptyFluidHandler;
import net.neoforged.neoforge.server.ServerLifecycleHooks;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.cyclops.cyclopscore.datastructure.DimPos;
import org.cyclops.cyclopscore.helper.BlockEntityHelpers;
import org.cyclops.cyclopscore.helper.FluidHelpers;
import org.cyclops.cyclopscore.helper.LocationHelpers;
import org.cyclops.cyclopscore.helper.MinecraftHelpers;
import org.cyclops.integrateddynamics.Capabilities;
import org.cyclops.integrateddynamics.GeneralConfig;
import org.cyclops.integrateddynamics.IntegratedDynamics;
import org.cyclops.integrateddynamics.Reference;
import org.cyclops.integrateddynamics.api.evaluate.EvaluationException;
import org.cyclops.integrateddynamics.api.evaluate.IValueInterface;
import org.cyclops.integrateddynamics.api.evaluate.variable.IValue;
import org.cyclops.integrateddynamics.api.network.IEnergyConsumingNetworkElement;
import org.cyclops.integrateddynamics.api.part.PartPos;
import org.cyclops.integrateddynamics.api.part.PartTarget;
import org.cyclops.integrateddynamics.api.part.aspect.AspectUpdateType;
import org.cyclops.integrateddynamics.api.part.aspect.IAspectRead;
import org.cyclops.integrateddynamics.api.part.aspect.IAspectRegistry;
import org.cyclops.integrateddynamics.api.part.aspect.IAspectWrite;
import org.cyclops.integrateddynamics.api.part.aspect.property.IAspectProperties;
import org.cyclops.integrateddynamics.core.evaluate.operator.*;
import org.cyclops.integrateddynamics.core.evaluate.variable.*;
import org.cyclops.integrateddynamics.core.helper.EnergyHelpers;
import org.cyclops.integrateddynamics.core.helper.Helpers;
import org.cyclops.integrateddynamics.core.helper.L10NValues;
import org.cyclops.integrateddynamics.core.part.aspect.build.AspectBuilder;
import org.cyclops.integrateddynamics.core.part.aspect.build.IAspectValuePropagator;
import org.cyclops.integrateddynamics.network.packet.SpeakTextPacket;
import org.cyclops.integrateddynamics.part.aspect.read.AspectReadBuilders;
import org.cyclops.integrateddynamics.part.aspect.write.AspectWriteBuilders;

import java.util.*;

/**
 * Collection of all aspects.
 * @author rubensworks
 */
public class Aspects {

    public static final IAspectRegistry REGISTRY = IntegratedDynamics._instance.getRegistryManager().getRegistry(IAspectRegistry.class);

    public static void load() {}

    public static final class Read {

        public static final class Audio {

            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_HARP_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.HARP)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_BASEDRUM_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.BASEDRUM)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_SNARE_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.SNARE)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_HAT_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.HAT)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_BASS_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.BASS)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_FLUTE_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.FLUTE)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_BELL_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.BELL)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_GUITAR_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.GUITAR)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_CHIME_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.CHIME)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_XYLOPHONE_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.XYLOPHONE)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_IRON_XYLOPHONE_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.IRON_XYLOPHONE)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_COW_BELL_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.COW_BELL)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_DIDGERIDOO_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.DIDGERIDOO)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_BIT_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.BIT)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_BANJO_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.BANJO)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_PLING_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.PLING)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_ZOMBIE_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.ZOMBIE)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_SKELETON_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.SKELETON)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_CREEPER_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.CREEPER)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_DRAGON_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.DRAGON)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_WITHER_SKELETON_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.WITHER_SKELETON)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_PIGLIN_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.PIGLIN)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_CUSTOM_HEAD_NOTE =
                    AspectReadBuilders.Audio.forInstrument(NoteBlockInstrument.CUSTOM_HEAD)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER).buildRead();

        }

        public static final class Block {
            public static final IAspectRead<ValueTypeBoolean.ValueBoolean, ValueTypeBoolean> BOOLEAN_BLOCK =
                    AspectReadBuilders.Block.BUILDER_BOOLEAN.handle(
                        dimPos -> dimPos.getLevel(true).getBlockState(dimPos.getBlockPos()).getBlock() != Blocks.AIR
                    ).withUpdateType(AspectUpdateType.BLOCK_UPDATE)
                            .handle(AspectReadBuilders.PROP_GET_BOOLEAN, "block").buildRead();
            public static final IAspectRead<ValueTypeString.ValueString, ValueTypeString> STRING_DIMENSION =
                    AspectReadBuilders.Block.BUILDER_STRING.handle(AspectReadBuilders.World.PROP_GET_WORLD).handle(
                            world -> world.dimension().location().toString()
                    ).withUpdateType(AspectUpdateType.NEVER)
                            .handle(AspectReadBuilders.PROP_GET_STRING, "dimension").buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_POSX =
                    AspectReadBuilders.Block.BUILDER_INTEGER.handle(AspectReadBuilders.World.PROP_GET_POS).handle(
                        BlockPos::getX
                    ).withUpdateType(AspectUpdateType.NEVER)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER, "posx").buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_POSY =
                    AspectReadBuilders.Block.BUILDER_INTEGER.handle(AspectReadBuilders.World.PROP_GET_POS).handle(
                        BlockPos::getY
                    ).withUpdateType(AspectUpdateType.NEVER)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER, "posy").buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_POSZ =
                    AspectReadBuilders.Block.BUILDER_INTEGER.handle(AspectReadBuilders.World.PROP_GET_POS).handle(
                        BlockPos::getZ
                    ).withUpdateType(AspectUpdateType.NEVER)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER, "posz").buildRead();
            public static final IAspectRead<ValueObjectTypeBlock.ValueBlock, ValueObjectTypeBlock> BLOCK =
                    AspectReadBuilders.Block.BUILDER_BLOCK
                            .handle(
                        dimPos -> dimPos.getLevel(true).getBlockState(dimPos.getBlockPos())
                    ).withUpdateType(AspectUpdateType.BLOCK_UPDATE)
                            .handle(AspectReadBuilders.PROP_GET_BLOCK).buildRead();
            public static final IAspectRead<ValueTypeNbt.ValueNbt, ValueTypeNbt> NBT =
                    AspectReadBuilders.Block.BUILDER_NBT.handle(dimPos -> {
                        BlockEntity tile = dimPos.getLevel(true).getBlockEntity(dimPos.getBlockPos());
                        try {
                            if (tile != null) {
                                return Optional.<Tag>of(tile.saveWithFullMetadata(tile.getLevel().registryAccess()));
                            }
                        } catch (Exception e) {
                            // Catch possible errors
                        }
                        return Optional.<Tag>empty();
                    }).handle(AspectReadBuilders.PROP_GET_NBT, "tile").buildRead();
            public static final IAspectRead<ValueTypeString.ValueString, ValueTypeString> STRING_BIOME =
                    AspectReadBuilders.Block.BUILDER_STRING
                            .handle(
                                    // Copied from DebugScreenOverlay#printBiome
                                    dimPos -> dimPos.getLevel(true).getBiome(dimPos.getBlockPos())
                                            .unwrap().map(key -> key.location().toString(), biome -> "[unregistered " + biome + "]")
                            ).withUpdateType(AspectUpdateType.BLOCK_UPDATE)
                            .handle(AspectReadBuilders.PROP_GET_STRING, "biome").buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_LIGHT =
                    AspectReadBuilders.Block.BUILDER_INTEGER.handle(dimPos -> dimPos.getLevel(true).getMaxLocalRawBrightness(dimPos.getBlockPos()))
                            .handle(AspectReadBuilders.PROP_GET_INTEGER, "light").buildRead();
        }

        public static final class Entity {
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_ITEMFRAMEROTATION =
                    AspectReadBuilders.Entity.BUILDER_INTEGER_ALL
                            .handle(AspectReadBuilders.World.PROP_GET_ITEMFRAME)
                            .handle(itemFrame -> itemFrame != null ? itemFrame.getRotation() : 0)
                            .handle(AspectReadBuilders.PROP_GET_INTEGER, "itemframerotation").buildRead();
            public static final IAspectRead<ValueTypeList.ValueList, ValueTypeList> LIST_ENTITIES =
                    AspectReadBuilders.Entity.BUILDER_LIST.handle(dimPos -> {
                        List<net.minecraft.world.entity.Entity> entities = dimPos.getLevel(true).getEntities((net.minecraft.world.entity.Entity) null,
                                new AABB(Vec3.atLowerCornerOf(dimPos.getBlockPos()), Vec3.atLowerCornerOf(dimPos.getBlockPos().offset(1, 1, 1))), EntitySelector.NO_SPECTATORS);
                        return ValueTypeList.ValueList.ofList(ValueTypes.OBJECT_ENTITY, Lists.transform(entities,
                            ValueObjectTypeEntity.ValueEntity::of
                        ));
                    }).appendKind("entities").buildRead();
            public static final IAspectRead<ValueTypeList.ValueList, ValueTypeList> LIST_PLAYERS =
                    AspectReadBuilders.Entity.BUILDER_LIST.handle(dimPos -> {
                        List<net.minecraft.world.entity.Entity> entities = dimPos.getLevel(true).getEntities((net.minecraft.world.entity.Entity) null,
                                new AABB(Vec3.atLowerCornerOf(dimPos.getBlockPos()), Vec3.atLowerCornerOf(dimPos.getBlockPos().offset(1, 1, 1))), Helpers.SELECTOR_IS_PLAYER);
                        return ValueTypeList.ValueList.ofList(ValueTypes.OBJECT_ENTITY, Lists.transform(entities,
                            ValueObjectTypeEntity.ValueEntity::of
                        ));
                    }).appendKind("players").buildRead();

            public static final IAspectRead<ValueObjectTypeEntity.ValueEntity, ValueObjectTypeEntity> ENTITY =
                    AspectReadBuilders.Entity.BUILDER_ENTITY.withProperties(AspectReadBuilders.LIST_PROPERTIES).handle(input -> {
                        int i = input.getRight().getValue(AspectReadBuilders.PROPERTY_LISTINDEX).getRawValue();
                        DimPos dimPos = input.getLeft().getTarget().getPos();
                        List<net.minecraft.world.entity.Entity> entities = dimPos.getLevel(true).getEntities((net.minecraft.world.entity.Entity) null,
                                new AABB(Vec3.atLowerCornerOf(dimPos.getBlockPos()), Vec3.atLowerCornerOf(dimPos.getBlockPos().offset(1, 1, 1))), EntitySelector.NO_SPECTATORS);
                        return ValueObjectTypeEntity.ValueEntity.of(i < entities.size() ? entities.get(i) : null);
                    }).buildRead();

            public static final IAspectRead<ValueObjectTypeItemStack.ValueItemStack, ValueObjectTypeItemStack> ITEMSTACK_ITEMFRAMECONTENTS =
                    AspectReadBuilders.Entity.BUILDER_ITEMSTACK
                            .handle(AspectReadBuilders.World.PROP_GET_ITEMFRAME)
                            .handle(itemFrame -> itemFrame != null ? itemFrame.getItem() : ItemStack.EMPTY)
                            .handle(AspectReadBuilders.PROP_GET_ITEMSTACK, "itemframecontents").buildRead();
        }

        public static final class ExtraDimensional {

            private static final Random RANDOM = new Random();

            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_RANDOM =
                    AspectReadBuilders.ExtraDimensional.BUILDER_INTEGER.handle(
                        minecraft -> RANDOM.nextInt()
                    ).handle(AspectReadBuilders.PROP_GET_INTEGER, "random").buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_PLAYERCOUNT =
                    AspectReadBuilders.ExtraDimensional.BUILDER_INTEGER.handle(
                        MinecraftServer::getPlayerCount
                    ).handle(AspectReadBuilders.PROP_GET_INTEGER, "playercount").buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_TICKTIME =
                    AspectReadBuilders.ExtraDimensional.BUILDER_INTEGER.handle(
                        minecraft -> (int) DoubleMath.mean(minecraft.getAverageTickTimeNanos())
                    ).handle(AspectReadBuilders.PROP_GET_INTEGER, "ticktime").buildRead();

            public static final IAspectRead<ValueTypeDouble.ValueDouble, ValueTypeDouble> DOUBLE_TPS =
                    AspectReadBuilders.ExtraDimensional.BUILDER_DOUBLE.handle(
                            minecraft -> Math.min(20, Stats.meanOf(minecraft.getTickTimesNanos()) / 1000)
                    ).handle(AspectReadBuilders.PROP_GET_DOUBLE, "tps").buildRead();

            public static final IAspectRead<ValueTypeList.ValueList, ValueTypeList> LIST_PLAYERS =
                    AspectReadBuilders.ExtraDimensional.BUILDER_LIST.handle(
                        minecraft -> ValueTypeList.ValueList.ofList(ValueTypes.OBJECT_ENTITY, Lists.transform(minecraft.getPlayerList().getPlayers(),
                                ValueObjectTypeEntity.ValueEntity::of))
                    ).appendKind("players").buildRead();

        }

        public static final class Fluid {

            public static final IAspectRead<ValueTypeBoolean.ValueBoolean, ValueTypeBoolean> BOOLEAN_FULL =
                    AspectReadBuilders.Fluid.BUILDER_BOOLEAN.handle(tankInfo -> {
                        boolean allFull = true;
                        for (int i = 0; i < tankInfo.getTanks(); i++) {
                            if (tankInfo.getFluidInTank(i).isEmpty() && tankInfo.getTankCapacity(i) > 0
                                    || (!tankInfo.getFluidInTank(i).isEmpty() && tankInfo.getFluidInTank(i).getAmount() < tankInfo.getTankCapacity(i))) {
                                allFull = false;
                            }
                        }
                        return allFull;
                    }).handle(AspectReadBuilders.PROP_GET_BOOLEAN, "full").buildRead();
            public static final IAspectRead<ValueTypeBoolean.ValueBoolean, ValueTypeBoolean> BOOLEAN_EMPTY =
                    AspectReadBuilders.Fluid.BUILDER_BOOLEAN.handle(tankInfo -> {
                        for (int i = 0; i < tankInfo.getTanks(); i++) {
                            if (!tankInfo.getFluidInTank(i).isEmpty() && tankInfo.getTankCapacity(i) > 0
                                    || (!tankInfo.getFluidInTank(i).isEmpty() && tankInfo.getFluidInTank(i).getAmount() < tankInfo.getTankCapacity(i))) {
                                return false;
                            }
                        }
                        return true;
                    }).handle(AspectReadBuilders.PROP_GET_BOOLEAN, "empty").buildRead();
            public static final IAspectRead<ValueTypeBoolean.ValueBoolean, ValueTypeBoolean> BOOLEAN_NONEMPTY =
                    AspectReadBuilders.Fluid.BUILDER_BOOLEAN.handle(tankInfo -> {
                        boolean hasFluid = false;
                        for (int i = 0; i < tankInfo.getTanks(); i++) {
                            if (!tankInfo.getFluidInTank(i).isEmpty() && tankInfo.getFluidInTank(i).getAmount() > 0) {
                                hasFluid = true;
                            }
                        }
                        return hasFluid;
                    }).handle(AspectReadBuilders.PROP_GET_BOOLEAN, "nonempty").buildRead();
            public static final IAspectRead<ValueTypeBoolean.ValueBoolean, ValueTypeBoolean> BOOLEAN_APPLICABLE =
                    AspectReadBuilders.Fluid.BUILDER_BOOLEAN.handle(
                        tankInfo -> tankInfo.getTanks() > 0 && tankInfo != EmptyFluidHandler.INSTANCE
                    ).handle(AspectReadBuilders.PROP_GET_BOOLEAN, "applicable").buildRead();

            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_AMOUNT =
                    AspectReadBuilders.Fluid.BUILDER_INTEGER_ACTIVATABLE.handle(AspectReadBuilders.Fluid.PROP_GET_FLUIDSTACK).handle(
                            FluidStack::getAmount
                    ).handle(AspectReadBuilders.PROP_GET_INTEGER, "amount").buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_AMOUNTTOTAL =
                    AspectReadBuilders.Fluid.BUILDER_INTEGER.handle(tankInfo -> {
                        int amount = 0;
                        for (int i = 0; i < tankInfo.getTanks(); i++) {
                            try {
                                amount = Math.addExact(amount, tankInfo.getFluidInTank(i).getAmount());
                            } catch (ArithmeticException e) {
                                amount = Integer.MAX_VALUE;
                            }
                            if (amount == Integer.MAX_VALUE) {
                                break;
                            }
                        }
                        return amount;
                    }).handle(AspectReadBuilders.PROP_GET_INTEGER, "totalamount").buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_CAPACITY =
                    AspectReadBuilders.Fluid.BUILDER_INTEGER_ACTIVATABLE.handle(
                        tankInfo -> tankInfo != null ? tankInfo.getLeft().getTankCapacity(tankInfo.getRight()) : 0
                    ).handle(AspectReadBuilders.PROP_GET_INTEGER, "capacity").buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_CAPACITYTOTAL =
                    AspectReadBuilders.Fluid.BUILDER_INTEGER.handle(tankInfo -> {
                        int capacity = 0;
                        for (int i = 0; i < tankInfo.getTanks(); i++) {
                            try {
                                capacity = Math.addExact(capacity, tankInfo.getTankCapacity(i));
                            } catch (ArithmeticException e) {
                                capacity = Integer.MAX_VALUE;
                            }
                            if (capacity == Integer.MAX_VALUE) {
                                break;
                            }
                        }
                        return capacity;
                    }).handle(AspectReadBuilders.PROP_GET_INTEGER, "totalcapacity").buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_TANKS =
                    AspectReadBuilders.Fluid.BUILDER_INTEGER.handle(
                            IFluidHandler::getTanks
                    ).handle(AspectReadBuilders.PROP_GET_INTEGER, "tanks").buildRead();

            public static final IAspectRead<ValueTypeDouble.ValueDouble, ValueTypeDouble> DOUBLE_FILLRATIO =
                    AspectReadBuilders.Fluid.BUILDER_DOUBLE_ACTIVATABLE.handle(tankInfo -> {
                        if(tankInfo == null) {
                            return 0D;
                        }
                        double amount = tankInfo.getLeft().getFluidInTank(tankInfo.getRight()).getAmount();
                        return amount / (double) tankInfo.getLeft().getTankCapacity(tankInfo.getRight());
                    }).handle(AspectReadBuilders.PROP_GET_DOUBLE, "fillratio").buildRead();

            public static final IAspectRead<ValueTypeList.ValueList, ValueTypeList> LIST_TANKFLUIDS =
                    AspectReadBuilders.BUILDER_LIST.appendKind("fluid").handle(AspectReadBuilders.Fluid.PROP_GET_LIST_FLUIDSTACKS, "fluidstacks").buildRead();
            public static final IAspectRead<ValueTypeList.ValueList, ValueTypeList> LIST_TANKCAPACITIES =
                    AspectReadBuilders.BUILDER_LIST.appendKind("fluid").handle(AspectReadBuilders.Fluid.PROP_GET_LIST_CAPACITIES, "capacities").buildRead();

            public static final IAspectRead<ValueObjectTypeFluidStack.ValueFluidStack, ValueObjectTypeFluidStack> FLUIDSTACK =
                    AspectReadBuilders.BUILDER_OBJECT_FLUIDSTACK
                            .handle(AspectReadBuilders.Fluid.PROP_GET_ACTIVATABLE, "fluid").withProperties(AspectReadBuilders.Fluid.PROPERTIES)
                            .handle(AspectReadBuilders.Fluid.PROP_GET_FLUIDSTACK).handle(AspectReadBuilders.PROP_GET_FLUIDSTACK).buildRead();

            public static final IAspectRead<ValueObjectTypeFluidStack.ValueFluidStack, ValueObjectTypeFluidStack> BLOCK =
                    AspectReadBuilders.BUILDER_OBJECT_FLUIDSTACK
                            .handle(AspectReadBuilders.Block.PROP_GET, "block")
                            .handle(dimPos -> {
                                BlockState blockState = dimPos.getLevel(true).getBlockState(dimPos.getBlockPos());
                                net.minecraft.world.level.block.Block block = blockState.getBlock();
                                if (block instanceof LiquidBlock) {
                                    return new FluidStack(((LiquidBlock) block).fluid, FluidHelpers.BUCKET_VOLUME);
                                }
                                return FluidStack.EMPTY;
                            })
                            .handle(AspectReadBuilders.PROP_GET_FLUIDSTACK).buildRead();

        }

        public static final class Inventory {
            public static final IAspectRead<ValueTypeBoolean.ValueBoolean, ValueTypeBoolean> BOOLEAN_FULL =
                    AspectReadBuilders.Inventory.BUILDER_BOOLEAN.handle(inventory -> {
                        if(inventory != null) {
                            for (int i = 0; i < inventory.getSlots(); i++) {
                                ItemStack itemStack = inventory.getStackInSlot(i);
                                if (itemStack.isEmpty()) {
                                    return false;
                                }
                            }
                        }
                        return true;
                    }).handle(AspectReadBuilders.PROP_GET_BOOLEAN, "full").buildRead();
            public static final IAspectRead<ValueTypeBoolean.ValueBoolean, ValueTypeBoolean> BOOLEAN_EMPTY =
                    AspectReadBuilders.Inventory.BUILDER_BOOLEAN.handle(inventory -> {
                        if(inventory != null) {
                            for(int i = 0; i < inventory.getSlots(); i++) {
                                ItemStack itemStack = inventory.getStackInSlot(i);
                                if(!itemStack.isEmpty()) {
                                    return false;
                                }
                            }
                        }
                        return true;
                    }).handle(AspectReadBuilders.PROP_GET_BOOLEAN, "empty").buildRead();
            public static final IAspectRead<ValueTypeBoolean.ValueBoolean, ValueTypeBoolean> BOOLEAN_NONEMPTY =
                    AspectReadBuilders.Inventory.BUILDER_BOOLEAN.handle(inventory -> {
                        if(inventory != null) {
                            for(int i = 0; i < inventory.getSlots(); i++) {
                                ItemStack itemStack = inventory.getStackInSlot(i);
                                if(!itemStack.isEmpty()) {
                                    return true;
                                }
                            }
                        }
                        return false;
                    }).handle(AspectReadBuilders.PROP_GET_BOOLEAN, "nonempty").buildRead();
            public static final IAspectRead<ValueTypeBoolean.ValueBoolean, ValueTypeBoolean> BOOLEAN_APPLICABLE =
                    AspectReadBuilders.Inventory.BUILDER_BOOLEAN.handle(
                        Objects::nonNull
                    ).handle(AspectReadBuilders.PROP_GET_BOOLEAN, "applicable").buildRead();

            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_COUNT =
                    AspectReadBuilders.Inventory.BUILDER_INTEGER.handle(inventory -> {
                        int count = 0;
                        if(inventory != null) {
                            for (int i = 0; i < inventory.getSlots(); i++) {
                                ItemStack itemStack = inventory.getStackInSlot(i);
                                if (!itemStack.isEmpty()) {
                                    count += itemStack.getCount();
                                }
                            }
                        }
                        return count;
                    }).handle(AspectReadBuilders.PROP_GET_INTEGER, "count").buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_SLOTS =
                    AspectReadBuilders.Inventory.BUILDER_INTEGER.handle(
                        inventory -> inventory != null ? inventory.getSlots() : 0
                    ).handle(AspectReadBuilders.PROP_GET_INTEGER, "slots").buildRead();
            public static final IAspectRead<ValueTypeInteger.ValueInteger, ValueTypeInteger> INTEGER_SLOTSFILLED =
                    AspectReadBuilders.Inventory.BUILDER_INTEGER.handle(inventory -> {
                        int count = 0;
                        if(inventory != null) {
