package gregtech.api.multitileentity.base;

import static gregtech.GTMod.GT_FML_LOGGER;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;

import gregtech.api.enums.GTValues;
import gregtech.api.task.TaskHost;
import gregtech.api.task.TickableTask;
import gregtech.api.util.GTLog;
import gregtech.api.util.GTUtil;

public abstract class TickableMultiTileEntity extends MultiTileEntity implements TaskHost {

    /** Variable for seeing if the Tick Function is called right now. */
    public boolean isRunningTick = false;
    /** Gets set to true when the Block received a Block Update. */
    public boolean blockUpdated = false;
    /** Timer Value */
    protected long timer = 0;
    /** Variable for updating Data to the Client */
    private boolean sendClientData = false;

    private final Map<String, TickableTask<?>> tasks = new HashMap<>();

    public TickableMultiTileEntity() {
        super(true);
    }

    @Override
    public final void registerTask(@Nonnull TickableTask<?> task) {
        if (tasks.containsKey(task.getName())) {
            throw new IllegalStateException(String.format("Task with name %s is already registered", task.getName()));
        }
        tasks.put(task.getName(), task);
    }

    @Nullable
    public TickableTask<?> getTask(@Nonnull String name) {
        return tasks.get(name);
    }

    @Override
    public final void updateEntity() {
        isRunningTick = true;
        final boolean isServerSide = isServerSide();
        try {
            if (timer++ == 0) {
                markDirty();
                GTUtil.markChunkDirty(this);
                onFirstTick(isServerSide);
            }
            if (isDead()) {
                return;
            }
            onPreTick(timer, isServerSide);
            super.updateEntity();
            if (!isServerSide && needsUpdate) {
                worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
                needsUpdate = false;
            }
            onTick(timer, isServerSide);
            for (TickableTask<?> task : tasks.values()) {
                task.update(timer, isServerSide);
            }
            if (isServerSide && timer > 2 && sendClientData) {
                sendClientData(null);
            }
            onPostTick(timer, isServerSide);

        } catch (Throwable e) {
            GT_FML_LOGGER.error("UpdateEntity Failed", e);
            e.printStackTrace(GTLog.err);
            try {
                onTickFailed(timer, isServerSide);
            } catch (Throwable e2) {
                GT_FML_LOGGER.error("UpdateEntity:onTickFailed Failed", e);
            }
        }

        isRunningTick = false;
    }

    @Override
    public void sendClientData(EntityPlayerMP aPlayer) {
        if (sendClientData) {
            // GT_FML_LOGGER.info("Sending client data");
            super.sendClientData(aPlayer);
            sendClientData = false;
        }
    }

    /**
     * The very first Tick happening to this TileEntity.
     */
    public void onFirstTick(boolean isServerSide) {
        if (isServerSide) {
            checkDropCover();
        } else {
            requestCoverDataIfNeeded();
        }
    }

    /**
     * The first part of the Tick, before block update.
     */
    public void onPreTick(long tick, boolean isServerSide) {}

    /**
     * The regular Tick. After block update, before sending data to client.
     */
    public void onTick(long tick, boolean isServerSide) {}

    /**
     * The absolute last part of the Tick, after sending data to client.
     */
    public void onPostTick(long tick, boolean isServerSide) {}

    /**
     * Gets called when there is an Exception/Error happening during one of the Tick methods.
     */
    public void onTickFailed(long tick, boolean isServerSide) {}

    @Override
    protected final void readTasksNBT(NBTTagCompound nbt) {
        if (nbt.hasKey(GTValues.NBT.TASKS)) {
            NBTTagCompound tasksTag = nbt.getCompoundTag(GTValues.NBT.TASKS);
            for (TickableTask<?> task : tasks.values()) {
                if (tasksTag.hasKey(task.getName())) {
                    task.readFromNBT(tasksTag.getCompoundTag(task.getName()));
                }
            }
        }
    }

    @Override
    protected final void writeTasksNBT(NBTTagCompound aNBT) {
        NBTTagCompound tasksTag = new NBTTagCompound();
        for (TickableTask<?> task : tasks.values()) {
            NBTTagCompound tag = new NBTTagCompound();
            task.writeToNBT(tag);
            tasksTag.setTag(task.getName(), tag);
        }
        aNBT.setTag(GTValues.NBT.TASKS, tasksTag);
    }

    @Override
    public void onNeighborBlockChange(World aWorld, Block aBlock) {
        blockUpdated = true;
    }

    @Override
    public void issueClientUpdate() {
        sendClientData = true;
        sendGraphicPacket();
    }

    @Override
    public byte getComparatorValue(ForgeDirection side) {
        return 0;
    }
}