/*
 * Copyright (c) 2018-2020 bartimaeusnek Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
 * conditions: The above copyright notice and this permission notice shall be included in all copies or substantial
 * portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

package bartworks.common.tileentities.classic;

import java.util.Arrays;
import java.util.Optional;

import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityFurnace;
import net.minecraft.util.StatCollector;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidEvent;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidTankInfo;
import net.minecraftforge.fluids.IFluidHandler;
import net.minecraftforge.fluids.IFluidTank;

import com.gtnewhorizons.modularui.api.ModularUITextures;
import com.gtnewhorizons.modularui.api.forge.IItemHandlerModifiable;
import com.gtnewhorizons.modularui.api.forge.InvWrapper;
import com.gtnewhorizons.modularui.api.screen.ITileWithModularUI;
import com.gtnewhorizons.modularui.api.screen.ModularWindow;
import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
import com.gtnewhorizons.modularui.common.widget.ProgressBar;
import com.gtnewhorizons.modularui.common.widget.SlotWidget;

import bartworks.API.ITileAddsInformation;
import bartworks.API.ITileDropsContent;
import bartworks.API.ITileHasDifferentTextureSides;
import bartworks.API.modularUI.BWUITextures;
import bartworks.MainMod;
import bartworks.common.configs.Configuration;
import gregtech.api.util.GTUtility;
import gregtech.common.pollution.Pollution;
import gregtech.common.pollution.PollutionConfig;

public class TileEntityHeatedWaterPump extends TileEntity implements ITileDropsContent, IFluidHandler, IFluidTank,
    ITileWithModularUI, ITileAddsInformation, ITileHasDifferentTextureSides {

    public static final int FUELSLOT = 0;
    public static final Fluid WATER = FluidRegistry.WATER;
    public ItemStack fuelstack;
    public FluidStack outputstack = new FluidStack(FluidRegistry.WATER, 0);
    public int fuel;
    public byte tick;
    public int maxfuel;
    public ItemStack fakestack = new ItemStack(Blocks.water);

    @Override
    public void writeToNBT(NBTTagCompound compound) {
        NBTTagCompound subItemStack = new NBTTagCompound();
        if (this.fuelstack != null) {
            this.fuelstack.writeToNBT(subItemStack);
        }
        compound.setTag("ItemStack", subItemStack);
        NBTTagCompound subFluidStack = new NBTTagCompound();
        this.outputstack.writeToNBT(subFluidStack);
        compound.setTag("FluidStack", subFluidStack);
        compound.setInteger("fuel", this.fuel);
        compound.setInteger("maxfuel", this.maxfuel);
        compound.setByte("tick", this.tick);
        super.writeToNBT(compound);
    }

    @Override
    public void readFromNBT(NBTTagCompound compound) {
        this.tick = compound.getByte("tick");
        this.fuel = compound.getInteger("fuel");
        this.maxfuel = compound.getInteger("maxfuel");
        this.outputstack = FluidStack.loadFluidStackFromNBT(compound.getCompoundTag("FluidStack"));
        if (!compound.getCompoundTag("ItemStack")
            .equals(new NBTTagCompound()))
            this.fuelstack = ItemStack.loadItemStackFromNBT(compound.getCompoundTag("ItemStack"));
        super.readFromNBT(compound);
    }

    private boolean checkPreUpdate() {
        return (this.fuelstack == null || this.fuelstack.stackSize <= 0) && this.fuel <= 0;
    }

    private void fixUnderlflow() {
        if (this.fuel < 0) this.fuel = 0;
    }

    private void handleRefuel() {
        if (this.fuelstack != null && this.fuel == 0) {
            this.fuel = this.maxfuel = TileEntityFurnace.getItemBurnTime(this.fuelstack);
            --this.fuelstack.stackSize;
            if (this.fuelstack.stackSize <= 0) this.fuelstack = fuelstack.getItem()
                .getContainerItem(fuelstack);
        }
    }

    private void handleWaterGeneration() {
        if (this.fuel > 0) {
            ++this.tick;
            --this.fuel;
            if (this.tick % 20 == 0) {
                if (this.outputstack.amount <= 8000 - Configuration.singleBlocks.mbWaterperSec)
                    this.outputstack.amount += Configuration.singleBlocks.mbWaterperSec;
                this.tick = 0;
            }
        }
    }

    @Override
    public void updateEntity() {
        if (this.worldObj.isRemote) return;

        this.pushWaterToAdjacentTiles();
        this.fakestack.setStackDisplayName(this.outputstack.amount + "L Water");
        if (this.checkPreUpdate()) return;

        this.fixUnderlflow();
        this.handleRefuel();
        this.handleWaterGeneration();
        this.causePollution();
    }

    private void pushWaterToAdjacentTiles() {
        Arrays.stream(ForgeDirection.values(), 0, 6) // All but Unknown
            .forEach(
                direction -> Optional
                    .ofNullable(
                        this.worldObj.getTileEntity(
                            this.xCoord + direction.offsetX,
                            this.yCoord + direction.offsetY,
                            this.zCoord + direction.offsetZ))
                    .ifPresent(te -> {
                        if (te instanceof IFluidHandler tank) {
                            if (tank.canFill(direction.getOpposite(), this.outputstack.getFluid())) {
                                int drainage = tank.fill(direction.getOpposite(), this.outputstack, false);
                                if (drainage > 0) {
                                    tank.fill(direction.getOpposite(), this.outputstack, true);
                                    this.drain(drainage, true);
                                }
                            }
                        } else if (te instanceof IFluidTank tank) {
                            int drainage = tank.fill(this.outputstack, false);
                            if (drainage > 0) {
                                tank.fill(this.outputstack, true);
                                this.drain(drainage, true);
                            }
                        }
                    }));
    }

    private void causePollution() {
        Optional.ofNullable(this.worldObj)
            .ifPresent(e -> {
                if (e.getTotalWorldTime() % 20 == 0) {
                    Optional.ofNullable(e.getChunkFromBlockCoords(this.xCoord, this.zCoord))
                        .ifPresent(c -> Pollution.addPollution(c, PollutionConfig.pollutionHeatedWaterPumpSecond));
                }
            });
    }

    @Override
    public int[] getAccessibleSlotsFromSide(int side) {
        return new int[] { 0 };
    }

    @Override
    public boolean canInsertItem(int p_102007_1_, ItemStack p_102007_2_, int p_102007_3_) {
        return TileEntityFurnace.getItemBurnTime(p_102007_2_) > 0;
    }

    @Override
    public boolean canExtractItem(int p_102008_1_, ItemStack p_102008_2_, int p_102008_3_) {
        return false;
    }

    @Override
    public int getSizeInventory() {
        return 2;
    }

    @Override
    public ItemStack getStackInSlot(int slotIn) {
        if (slotIn == 0) return this.fuelstack;
        return this.fakestack;
    }

    @Override
    public ItemStack decrStackSize(int slot, int ammount) {
        if (slot != TileEntityHeatedWaterPump.FUELSLOT || this.fuelstack == null || ammount > this.fuelstack.stackSize)
            return null;

        return this.fuelstack.splitStack(ammount);
    }

    @Override
    public ItemStack getStackInSlotOnClosing(int index) {
        return null;
    }

    @Override
    public void setInventorySlotContents(int slot, ItemStack stack) {
        if (slot == TileEntityHeatedWaterPump.FUELSLOT) this.fuelstack = stack;
        else this.fakestack = stack;
    }

    @Override
    public String getInventoryName() {
        return null;
    }

    @Override
    public boolean hasCustomInventoryName() {
        return false;
    }

    @Override
    public int getInventoryStackLimit() {
        return 64;
    }

    @Override
    public boolean isUseableByPlayer(EntityPlayer player) {
        return true;
    }

    @Override
    public void openInventory() {}

    @Override
    public void closeInventory() {}

    @Override
    public boolean isItemValidForSlot(int index, ItemStack stack) {
        return TileEntityFurnace.getItemBurnTime(stack) > 0 && index == TileEntityHeatedWaterPump.FUELSLOT;
    }

    @Override
    public FluidStack getFluid() {
        return this.outputstack.amount > 0 ? this.outputstack : null;
    }

    @Override
    public int getFluidAmount() {
        return this.outputstack.amount;
    }

    @Override
    public int getCapacity() {
        return 8000;
    }

    @Override
    public FluidTankInfo getInfo() {
        return new FluidTankInfo(this);
    }

    @Override
    public int fill(FluidStack resource, boolean doFill) {
        return 0;
    }

    @Override
    public FluidStack drain(int maxDrain, boolean doDrain) {
        int actualdrain = maxDrain;
        if (actualdrain > this.outputstack.amount) actualdrain = this.outputstack.amount;
        FluidStack ret = new FluidStack(TileEntityHeatedWaterPump.WATER, actualdrain);
        if (ret.amount == 0) ret = null;
        if (doDrain) {
            this.outputstack.amount -= actualdrain;
            FluidEvent.fireEvent(
                new FluidEvent.FluidDrainingEvent(
                    this.outputstack,
                    this.getWorldObj(),
                    this.xCoord,
                    this.yCoord,
                    this.zCoord,
                    this,
                    actualdrain));
        }
        return ret;
    }

    @Override
    public int fill(ForgeDirection from, FluidStack resource, boolean doFill) {
        return 0;
    }

    @Override
    public FluidStack drain(ForgeDirection from, FluidStack resource, boolean doDrain) {
        if (resource != null && resource.getFluid() == TileEntityHeatedWaterPump.WATER
            && this.drain(resource.amount, false) != null) return this.drain(resource.amount, doDrain);
        return null;
    }

    @Override
    public FluidStack drain(ForgeDirection from, int maxDrain, boolean doDrain) {
        return this.drain(maxDrain, doDrain);
    }

    @Override
    public boolean canFill(ForgeDirection from, Fluid fluid) {
        return false;
    }

    @Override
    public boolean canDrain(ForgeDirection from, Fluid fluid) {
        return fluid == null || fluid == TileEntityHeatedWaterPump.WATER;
    }

    @Override
    public FluidTankInfo[] getTankInfo(ForgeDirection from) {
        return new FluidTankInfo[] { this.getInfo() };
    }

    @Override
    public String[] getInfoData() {
        return new String[] {
            StatCollector.translateToLocal("tooltip.tile.waterpump.0.name") + " "
                + GTUtility.formatNumbers(Configuration.singleBlocks.mbWaterperSec)
                + String.format(
                    StatCollector.translateToLocal("tooltip.tile.waterpump.1.name"),
                    PollutionConfig.pollutionHeatedWaterPumpSecond),
            StatCollector.translateToLocal("tooltip.tile.waterpump.2.name") };
    }

    @Override
    public void registerBlockIcons(IIconRegister par1IconRegister) {
        ITileHasDifferentTextureSides.texture[ForgeDirection.UP.ordinal()] = par1IconRegister
            .registerIcon(MainMod.MOD_ID + ":heatedWaterPumpTop");
        ITileHasDifferentTextureSides.texture[ForgeDirection.DOWN.ordinal()] = par1IconRegister
            .registerIcon(MainMod.MOD_ID + ":heatedWaterPumpDown");
        for (int i = 2; i < 7; i++) {
            ITileHasDifferentTextureSides.texture[i] = par1IconRegister
                .registerIcon(MainMod.MOD_ID + ":heatedWaterPumpSide");
        }
    }

    @Override
    public ModularWindow createWindow(UIBuildContext buildContext) {
        ModularWindow.Builder builder = ModularWindow.builder(176, 166);
        builder.setBackground(ModularUITextures.VANILLA_BACKGROUND);
        builder.bindPlayerInventory(buildContext.getPlayer());
        final IItemHandlerModifiable invWrapper = new InvWrapper(this);

        builder.widget(
            new SlotWidget(invWrapper, 0).setFilter(stack -> TileEntityFurnace.getItemBurnTime(stack) > 0)
                .setPos(55, 52))
            .widget(
                SlotWidget.phantom(invWrapper, 1)
                    .disableInteraction()
                    .setPos(85, 32))
            .widget(
                new ProgressBar().setProgress(() -> (float) this.fuel / this.maxfuel)
                    .setTexture(BWUITextures.PROGRESSBAR_FUEL, 14)
                    .setDirection(ProgressBar.Direction.UP)
                    .setPos(56, 36)
                    .setSize(14, 14));

        return builder.build();
    }
}