package cn.mcmod.tofucraft.api.tfenergy;

import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

@Mod.EventBusSubscriber
public class TofuNetwork {

    /*
     * Feature:
     * 1. Maintains a hashmap of all TofuEnergyStorageTileEntity(TESTE)
     * 2. Provides a set of helper methods to access TESTE.
     *
     * Tofu Network rules:
     * 1. Every tile is registered to the network, no matter what it is.
     *
     * 2. Machines are not able to radiate energy themselves, and not likely the production
     *    ones will too. The machines which should give out energy needs a antenna to act as
     *    a medium to radiate energy. But actually the working part is only the machine.
     *
     * 3. The consumer should be passive, as the network load will be much higher if every
     *    machine tried to access the map, the antenna should distribute the energy to other
     *    machines.
     * */

    public static final TofuNetwork Instance = new TofuNetwork();

    private HashMap<String, TileEntity> reference = new HashMap<>();

    public static List<String> toUUIDs(Stream<Map.Entry<String, TileEntity>> map) {
        List<String> uids = new ArrayList<>();
        map.forEach(entry -> uids.add(entry.getKey()));
        return uids;
    }

    public static List<TileEntity> toTiles(Stream<Map.Entry<String, TileEntity>> map) {
        List<TileEntity> tes = new ArrayList<>();
        map.forEach(entry -> tes.add(entry.getValue()));
        return tes;
    }

    //Clears out the tofu network cache
    @SubscribeEvent
    public static void onUnloadWorld(WorldEvent.Unload event) {
        World world = event.getWorld();
        for (String uid : toUUIDs(Instance.getTEWithinDim(world.provider.getDimension()))) {
            //It is a world unload, so isSystem is here to prevent bugs from misdetailed event.
            Instance.unload(uid, true);
        }
    }

    public TileEntity find(String uid) {
        return reference.get(uid);
    }

    public void register(String uid, TileEntity te) {
        register(uid, te, false);
    }

    public void register(String uid, TileEntity te, boolean isSystem) {
        if (!(te instanceof ITofuEnergy))
            throw new IllegalArgumentException("Can't register machine which is not Tofu Energy Tile!");
        reference.put(uid, te);
        MinecraftForge.EVENT_BUS.post(new TofuNetworkChangedEvent.NetworkLoaded(uid, te, isSystem));
    }

    public HashMap<String, TileEntity> getReference() {
        return reference;
    }

    public Stream<Map.Entry<String, TileEntity>> getTEWithinDim(int dimid) {
        return reference
                .entrySet()
                .stream()
                .filter(entry -> entry.getValue().getWorld().provider.getDimension() == dimid);
    }

    public Stream<Map.Entry<String, TileEntity>> getTEWithinRadius(World world, BlockPos pos, double radius) {
        return getTEWithinDim(world.provider.getDimension())
                .filter(entry -> entry.getValue().getPos().getDistance(pos.getX(), pos.getY(), pos.getZ()) <= radius);
    }

    public Stream<Map.Entry<String, TileEntity>> getTEWithinRadius(TileEntity center, double radius) {
        BlockPos pos = center.getPos();
        return getTEWithinDim(center.getWorld().provider.getDimension())
                .filter(entry -> entry.getValue().getPos().getDistance(pos.getX(), pos.getY(), pos.getZ()) <= radius);
    }

    public Stream<Map.Entry<String, TileEntity>> getExtractableWithinRadius(World world, BlockPos pos, double radius) {
        return getTEWithinRadius(world, pos, radius)
                .filter(entry -> ((ITofuEnergy) entry.getValue()).canDrain(null));
    }

    public Stream<Map.Entry<String, TileEntity>> getExtractableWithinRadius(TileEntity center, double radius) {
        if (!(center instanceof ITofuEnergy))
            throw new IllegalArgumentException("The center tile is not able to transfer energy!");
        return getTEWithinRadius(center, radius)
                .filter(entry -> ((ITofuEnergy) entry.getValue()).canDrain(center) && ((ITofuEnergy) center).canReceive(entry.getValue()));
    }

    public Stream<Map.Entry<String, TileEntity>> getInsertableWithinRadius(World world, BlockPos pos, double radius) {
        return getTEWithinRadius(world, pos, radius)
                .filter(entry -> ((ITofuEnergy) entry.getValue()).canReceive(null));
    }

    public Stream<Map.Entry<String, TileEntity>> getInsertableWithinRadius(TileEntity center, double radius) {
        if (!(center instanceof ITofuEnergy))
            throw new IllegalArgumentException("The center tile is not able to transfer energy!");
        return getTEWithinRadius(center, radius)
                .filter(entry -> ((ITofuEnergy) entry.getValue()).canReceive(center) && ((ITofuEnergy) center).canDrain(entry.getValue()));
    }

    public void unload(String uid) {
        unload(uid, false);
    }

    public void unload(String uid, boolean isSystem) {
        reference.remove(uid);
        MinecraftForge.EVENT_BUS.post(new TofuNetworkChangedEvent.NetworkRemoved(uid, reference.get(uid), isSystem));
    }

    public void clear() {
        clear(false);
    }

    public void clear(boolean isSystem) {
        reference.clear();
        MinecraftForge.EVENT_BUS.post(new TofuNetworkChangedEvent.NetworkCleared(isSystem));
    }
}