package com.acethewildfire.acesbs.block.entity.custom;

import com.acethewildfire.acesbs.block.custom.EntropicEviscerator;
import com.acethewildfire.acesbs.block.entity.ImplementedInventory;
import com.acethewildfire.acesbs.block.entity.ModBlockEntities;
import com.acethewildfire.acesbs.item.ModItems;
import com.acethewildfire.acesbs.recipe.EntropicEvisceratorRecipe;
import com.acethewildfire.acesbs.recipe.EntropicEvisceratorRecipeInput;
import com.acethewildfire.acesbs.recipe.ModRecipies;
import com.acethewildfire.acesbs.screen.custom.EntropicEvisceratorScreenHandler;
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventories;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.listener.ClientPlayPacketListener;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket;
import net.minecraft.recipe.RecipeEntry;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.screen.PropertyDelegate;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;

import java.util.Optional;

public class EntropicEvisceratorEntity extends BlockEntity implements ImplementedInventory, ExtendedScreenHandlerFactory<BlockPos> {
    private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(3, ItemStack.EMPTY);
    private Direction facing;

    private static final int INPUT_SLOT = 0;
    private static final int FUEL_SLOT = 1;
    private static final int OUTPUT_SLOT = 2;

    private static final int FUEL_PROGRESS = 0;
    private static final int FUEL_MAX_PROGRESS = 1;
    private static final int EVISCERATE_PROGRESS = 2;
    private static final int EVISCERATE_MAX_PROGRESS = 3;

    private int fuel_progress = -1;
    private int fuel_max_progress = 400;
    private int eviscerate_progress = 0;
    private int eviscerate_max_progress = 200;

    protected final PropertyDelegate propertyDelegate;

    public EntropicEvisceratorEntity(BlockPos pos, BlockState state) {
        super(ModBlockEntities.E_EVISCERATOR_BE, pos, state);
        this.facing = state.get(EntropicEviscerator.FACING);
        this.propertyDelegate = new PropertyDelegate() {
            @Override
            public int get(int index) {
                return switch (index) {
                    case 0 -> EntropicEvisceratorEntity.this.fuel_progress;
                    case 1 -> EntropicEvisceratorEntity.this.fuel_max_progress;
                    case 2 -> EntropicEvisceratorEntity.this.eviscerate_progress;
                    case 3 -> EntropicEvisceratorEntity.this.eviscerate_max_progress;
                    default -> 0;
                };
            }

            @Override
            public void set(int index, int value) {
                switch (index) {
                    case 0 -> EntropicEvisceratorEntity.this.fuel_progress = value;
                    case 1 -> EntropicEvisceratorEntity.this.fuel_max_progress = value;
                    case 2 -> EntropicEvisceratorEntity.this.eviscerate_progress = value;
                    case 3 -> EntropicEvisceratorEntity.this.eviscerate_max_progress = value;
                }
            }

            @Override
            public int size() {
                return 4;
            }
        };
    }

    public Direction getFacing() {
        return facing;
    }

    public void setFacing(Direction newFacing) {
        this.facing = newFacing;
        // sync to blockstate for rendering
        if (world != null && !world.isClient) {
            world.setBlockState(pos, getCachedState().with(EntropicEviscerator.FACING, newFacing), 3);
            markDirty();
        }
    }

    @Override
    protected void writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        super.writeNbt(nbt, registryLookup);
        Inventories.writeNbt(nbt, inventory, registryLookup);
        nbt.putInt("Facing", facing.getId());
        nbt.putInt("eviscerator.fuel_progress", fuel_progress);
        nbt.putInt("eviscerator.fuel_max_progress", fuel_max_progress);
        nbt.putInt("eviscerator.eviscerate_progress", eviscerate_progress);
        nbt.putInt("eviscerator.eviscerate_max_progress", eviscerate_max_progress);
    }

    @Override
    protected void readNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) {
        Inventories.readNbt(nbt, inventory, registryLookup);
        this.facing = Direction.byId(nbt.getInt("Facing"));
        this.fuel_progress = nbt.getInt("eviscerator.fuel_progress");
        this.fuel_max_progress = nbt.getInt("eviscerator.fuel_max_progress");
        this.eviscerate_progress = nbt.getInt("eviscerator.eviscerate_progress");
        this.eviscerate_max_progress = nbt.getInt("eviscerator.eviscerate_max_progress");
        super.readNbt(nbt, registryLookup);
    }

    @Override
    public DefaultedList<ItemStack> getItems() {
        return inventory;
    }

    @Override
    public BlockPos getScreenOpeningData(ServerPlayerEntity serverPlayerEntity) {
        return this.pos;
    }

    @Override
    public Text getDisplayName() {
        return Text.translatable("block.acesbs.entropic_eviscerator");
    }

    @Override
    public @Nullable ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) {
        return new EntropicEvisceratorScreenHandler(syncId, playerInventory, this, propertyDelegate);
    }

    public float getCookProgress() {
        int i = this.propertyDelegate.get(EVISCERATE_PROGRESS);
        int j = this.propertyDelegate.get(EVISCERATE_MAX_PROGRESS);
        return j != 0 && i != 0 ? MathHelper.clamp((float)i / (float)j, 0.0F, 1.0F) : 0.0F;
    }

    public float getFuelProgress() {
        int i = this.propertyDelegate.get(FUEL_MAX_PROGRESS);
        if (i == 0) {
            i = 200;
        }

        return MathHelper.clamp((float)this.propertyDelegate.get(FUEL_PROGRESS) / (float) i, 0.0F, 1.0F);
    }

//    public boolean canInsertIntoSlot(int index) {
//        return index != 1;
//    }

    public void tick(World world, BlockPos pos, BlockState state) {
        if(hasRecipe()) {
            if (hasFuel() || hasUnfinishedFuel()){
                if (this.fuel_progress == -1){
                    // No fuel has been used yet
                    consumeFuel();
                    resetFuelProgress();
                }
                increaseCraftingProgress();
                markDirty(world, pos, state);

                if(hasCraftingFinished()) {
                    craftItem();
                    resetProgress();
                }

                if(hasFuel() && hasFuelFinished()) {
                    consumeFuel();
                    resetFuelProgress();
                }
            }
        } else {
            resetProgress();
        }
    }

    private boolean hasFuel() {
        return this.getStack(FUEL_SLOT).isOf(ModItems.STABLE_ENTROPY);
    }

    private boolean hasUnfinishedFuel() {
        return this.fuel_progress > -1 && !hasFuelFinished();
    }

    private void resetProgress() {
        this.eviscerate_progress = 0;
        this.eviscerate_max_progress = 200;
    }

    private void resetFuelProgress() {
        this.fuel_progress = 0;
        this.fuel_max_progress = 400;
    }

    private void craftItem() {
        Optional<RecipeEntry<EntropicEvisceratorRecipe>> recipe = getCurrentRecipe();
        ItemStack output = recipe.get().value().output();

        this.removeStack(INPUT_SLOT, 1);
        this.setStack(OUTPUT_SLOT, new ItemStack(output.getItem(),
                this.getStack(OUTPUT_SLOT).getCount() + output.getCount()));
    }

    private void consumeFuel() {
        this.setStack(FUEL_SLOT, new ItemStack(this.getStack(FUEL_SLOT).getItem(),
                this.getStack(FUEL_SLOT).getCount() - 1));
    }

    private boolean hasCraftingFinished() {
        return this.eviscerate_progress >= this.eviscerate_max_progress;
    }

    private boolean hasFuelFinished() {
        return this.fuel_progress >= this.fuel_max_progress;
    }

    private void increaseCraftingProgress() {
        this.eviscerate_progress++;
        this.fuel_progress++;
    }

    private boolean hasRecipe() {
        Optional<RecipeEntry<EntropicEvisceratorRecipe>> recipe = getCurrentRecipe();

        if (recipe.isEmpty()){ return false; }

        ItemStack output = recipe.get().value().output();

        return canInsertAmountIntoOutputSlot(output.getCount()) && canInsertItemIntoOutputSlot(output);
    }

    private Optional<RecipeEntry<EntropicEvisceratorRecipe>> getCurrentRecipe() {
        return this.getWorld().getRecipeManager()
                .getFirstMatch(ModRecipies.ENTROPIC_EVISCERATOR_TYPE, new EntropicEvisceratorRecipeInput(inventory.get(INPUT_SLOT)), this.getWorld());
    }

    private boolean canInsertItemIntoOutputSlot(ItemStack output) {
        return this.getStack(OUTPUT_SLOT).isEmpty() || this.getStack(OUTPUT_SLOT).getItem() == output.getItem();
    }

    private boolean canInsertAmountIntoOutputSlot(int count) {
        int maxCount = this.getStack(OUTPUT_SLOT).isEmpty() ? 64 : this.getStack(OUTPUT_SLOT).getMaxCount();
        int currentCount = this.getStack(OUTPUT_SLOT).getCount();

        return maxCount >= currentCount + count;
    }

    @Nullable
    @Override
    public Packet<ClientPlayPacketListener> toUpdatePacket() {
        return BlockEntityUpdateS2CPacket.create(this);
    }

    @Override
    public NbtCompound toInitialChunkDataNbt(RegistryWrapper.WrapperLookup registryLookup) {
        return createNbt(registryLookup);
    }
}
