package org.ultramine.mods.hawkeye;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.ultramine.mods.hawkeye.database.DataManager;
import org.ultramine.mods.hawkeye.entry.DataEntry;
import org.ultramine.mods.hawkeye.util.HawkUtil;

import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.world.World;
import net.minecraftforge.common.util.BlockSnapshot;

/**
 * Runnable class for performing a data rollback. This class should always be
 * run in a separate thread to avoid impacting on server performance
 * 
 * @author oliverw92
 */
public class Rollback
{
	private static final Logger log = LogManager.getLogger();
	private final PlayerSession session;
	private Iterator<DataEntry> rollbackQueue;
	private final List<DataEntry> undo = new ArrayList<DataEntry>();
	private RollbackType rollbackType = RollbackType.GLOBAL;

	/**
	 * @param session
	 *            {@link PlayerSession} to retrieve rollback results from
	 */
	public Rollback(RollbackType rollbackType, PlayerSession session)
	{
		this.rollbackType = rollbackType;
		this.session = session;
		rollbackQueue = session.getRollbackResults().iterator();

		//Check that we actually have results
		if(!rollbackQueue.hasNext())
		{
			HawkUtil.sendMessage(session.getSender(), "&cNo results found to rollback");
			return;
		}

		HawkUtil.debug("Starting rollback of " + session.getRollbackResults().size() + " results");

		//Start rollback
		session.setDoingRollback(true);
		HawkUtil.sendMessage(session.getSender(), "&cAttempting to rollback &7" + session.getRollbackResults().size() + "&c results");
		FMLCommonHandler.instance().bus().register(this);
	}

	/**
	 * Run the rollback. Contains appropriate methods of catching errors and
	 * notifying the player
	 */
	@SubscribeEvent
	public void onServerTick(TickEvent.ServerTickEvent e)
	{
		if(e.phase == TickEvent.Phase.END)
			return;
		//Start rollback process
		int i = 0;
		while(i < 200 && rollbackQueue.hasNext())
		{
			DataEntry entry = rollbackQueue.next();

			//If the action can't be rolled back, skip this entry
			if(entry.getType() == null || !entry.getType().canRollback())
				continue;

			//If the world doesn't exist, skip this entry
			World world = HawkEye.getWorld(entry.getWorld());
			if(world == null)
				continue;

			//Get some data from the entry
			BlockSnapshot state = BlockSnapshot.getBlockSnapshot(world, (int) entry.getX(), (int) entry.getY(), (int) entry.getZ());

			//Attempt global rollback
			if(rollbackType == RollbackType.GLOBAL)
			{
				boolean addUndo;
				try
				{
					addUndo = entry.rollback(world);
				}
				catch(Exception exc)
				{
					addUndo = true;
					log.error("Error while rollbacking entry at ["+entry.getWorld()+"]("+entry.getX()+", "+entry.getY()+", "+entry.getZ()+")", exc);
				}
				
				if(addUndo)
				{
					entry.setUndoState(state);
					undo.add(entry);
				}
			}
			//Local rollback preview
			else if(rollbackType == RollbackType.LOCAL && entry.rollbackPlayer(world, (EntityPlayerMP)session.getSender()))
			{
				entry.setUndoState(state);
				undo.add(entry);
			}
			
			i++;
		}

		//Check if rollback is finished
		if(!rollbackQueue.hasNext())
		{
			//End timer
			FMLCommonHandler.instance().bus().unregister(this);

			session.setDoingRollback(false);
			session.setRollbackResults(undo);

			//Store undo results and notify player
			if(rollbackType == RollbackType.GLOBAL)
			{
				HawkUtil.sendMessage(session.getSender(), "&cRollback complete, &7" + undo.size() + "&c edits performed");
				HawkUtil.sendMessage(session.getSender(), "&cUndo this rollback using &7/hawk undo");
				//Delete data if told to
				if(HawkEye.instance.config.general.deleteDataOnRollback)
					DataManager.deleteEntries(undo);
			}
			else
			{
				HawkUtil.sendMessage(session.getSender(), "&cRollback preview complete, &7" + undo.size() + "&c edits performed to you");
				HawkUtil.sendMessage(session.getSender(), "&cType &7/hawk apply&c to make these changes permanent or &7/hawk cancel&c to cancel");
			}

			HawkUtil.debug("Rollback complete, " + undo.size() + " edits performed");
		}
	}

	public enum RollbackType
	{
		GLOBAL, LOCAL
	}
}
