package codechicken.lib.world;

import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import net.minecraft.client.Minecraft;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.EmptyChunk;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.ChunkDataEvent;
import net.minecraftforge.event.world.ChunkEvent;
import net.minecraftforge.event.world.ChunkWatchEvent.UnWatch;
import net.minecraftforge.event.world.ChunkWatchEvent.Watch;
import net.minecraftforge.event.world.WorldEvent;

import java.util.ArrayList;
import java.util.HashMap;

public class WorldExtensionManager
{    
    public static class WorldExtensionEventHandler
    {
        @SubscribeEvent
        public void onChunkDataLoad(ChunkDataEvent.Load event)
        {
            if(!worldMap.containsKey(event.world))
                WorldExtensionManager.onWorldLoad(event.world);
            
            createChunkExtension(event.world, event.getChunk());

            for(WorldExtension extension : worldMap.get(event.world))
                extension.loadChunkData(event.getChunk(), event.getData());
        }

        @SubscribeEvent
        public void onChunkDataSave(ChunkDataEvent.Save event)
        {
            for(WorldExtension extension : worldMap.get(event.world))
                extension.saveChunkData(event.getChunk(), event.getData());
            
            if(!event.getChunk().isChunkLoaded)
                removeChunk(event.world, event.getChunk());
        }
        
        @SubscribeEvent
        public void onChunkLoad(ChunkEvent.Load event)
        {
            if(!worldMap.containsKey(event.world))
                WorldExtensionManager.onWorldLoad(event.world);
            
            createChunkExtension(event.world, event.getChunk());
            
            for(WorldExtension extension : worldMap.get(event.world))
                extension.loadChunk(event.getChunk());
        }

        @SubscribeEvent
        public void onChunkUnLoad(ChunkEvent.Unload event)
        {
            if(event.getChunk() instanceof EmptyChunk)
                return;
            
            for(WorldExtension extension : worldMap.get(event.world))
                extension.unloadChunk(event.getChunk());
            
            if(event.world.isRemote)
                removeChunk(event.world, event.getChunk());
        }

        @SubscribeEvent
        public void onWorldSave(WorldEvent.Save event)
        {
            if(worldMap.containsKey(event.world))
                for(WorldExtension extension : worldMap.get(event.world))
                    extension.save();
        }

        @SubscribeEvent
        public void onWorldLoad(WorldEvent.Load event)
        {
            if(!worldMap.containsKey(event.world))
                WorldExtensionManager.onWorldLoad(event.world);
        }

        @SubscribeEvent
        public void onWorldUnLoad(WorldEvent.Unload event)
        {
            if(worldMap.containsKey(event.world))//because force closing unloads a world twice
                for(WorldExtension extension : worldMap.remove(event.world))
                    extension.unload();
        }
        
        @SubscribeEvent
        public void onChunkWatch(Watch event)
        {            
            Chunk chunk = event.player.worldObj.getChunkFromChunkCoords(event.chunk.chunkXPos, event.chunk.chunkZPos);
            for(WorldExtension extension : worldMap.get(event.player.worldObj))
                extension.watchChunk(chunk, event.player);
        }

        @SubscribeEvent
        @SideOnly(Side.CLIENT)
        public void onChunkUnWatch(UnWatch event)
        {
            Chunk chunk = event.player.worldObj.getChunkFromChunkCoords(event.chunk.chunkXPos, event.chunk.chunkZPos);
            for(WorldExtension extension : worldMap.get(event.player.worldObj))
                extension.unwatchChunk(chunk, event.player);
        }

        @SubscribeEvent
        @SideOnly(Side.CLIENT)
        public void clientTick(TickEvent.ClientTickEvent event)
        {
            World world = Minecraft.getMinecraft().theWorld;
            if (worldMap.containsKey(world))
                if (event.phase == TickEvent.Phase.START)
                    preTick(world);
                else
                    postTick(world);
        }

        @SubscribeEvent
        public void serverTick(TickEvent.WorldTickEvent event)
        {
            if(!worldMap.containsKey(event.world))
                WorldExtensionManager.onWorldLoad(event.world);

            if(event.phase == TickEvent.Phase.START)
                preTick(event.world);
            else
                postTick(event.world);
        }
    }
    
    private static boolean initialised;
    private static ArrayList<WorldExtensionInstantiator> extensionIntialisers = new ArrayList<WorldExtensionInstantiator>();
    
    public static void registerWorldExtension(WorldExtensionInstantiator init)
    {
        if(!initialised)
            init();
        
        init.instantiatorID = extensionIntialisers.size();
        extensionIntialisers.add(init);
    }

    private static void init()
    {
        initialised = true;
        MinecraftForge.EVENT_BUS.register(new WorldExtensionEventHandler());
        FMLCommonHandler.instance().bus().register(new WorldExtensionEventHandler());
    }

    private static HashMap<World, WorldExtension[]> worldMap = new HashMap<World, WorldExtension[]>();
    
    private static void onWorldLoad(World world)
    {
        WorldExtension[] extensions = new WorldExtension[extensionIntialisers.size()];
        for(int i = 0; i < extensions.length; i++)
            extensions[i] = extensionIntialisers.get(i).createWorldExtension(world);
        
        worldMap.put(world, extensions);
        
        for(WorldExtension extension : extensions)
            extension.load();
    }

    private static void createChunkExtension(World world, Chunk chunk)
    {
        WorldExtension[] extensions = worldMap.get(world);
        for(int i = 0; i < extensionIntialisers.size(); i++)
            if(!extensions[i].containsChunk(chunk))
                extensions[i].addChunk(extensionIntialisers.get(i).createChunkExtension(chunk, extensions[i]));
    }
    
    private static void removeChunk(World world, Chunk chunk)
    {
        for(WorldExtension extension : worldMap.get(world))
            extension.remChunk(chunk);
    }
    
    private static void preTick(World world)
    {
        for(WorldExtension extension : worldMap.get(world))
            extension.preTick();
    }
    
    private static void postTick(World world)
    {
        for(WorldExtension extension : worldMap.get(world))
            extension.postTick();
    }

    public static WorldExtension getWorldExtension(World world, int instantiatorID)
    {
        return worldMap.get(world)[instantiatorID];
    }
}