package openperipheral.addons.glasses.client;

import com.google.common.base.Optional;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Lists;
import com.google.common.collect.Table;
import cpw.mods.fml.client.FMLClientHandler;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent;
import cpw.mods.fml.common.gameevent.TickEvent.Phase;
import cpw.mods.fml.common.network.FMLNetworkEvent.ClientDisconnectionFromServerEvent;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.StatCollector;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType;
import net.minecraftforge.event.entity.player.ItemTooltipEvent;
import openmods.geometry.Box2d;
import openperipheral.addons.glasses.GlassesEvent.GlassesChangeBackgroundEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesSetDragParamsEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesSetGuiVisibilityEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesSetKeyRepeatEvent;
import openperipheral.addons.glasses.GlassesEvent.GlassesStopCaptureEvent;
import openperipheral.addons.glasses.GuiCapture;
import openperipheral.addons.glasses.NbtGuidProviders;
import openperipheral.addons.glasses.SurfaceType;
import openperipheral.addons.glasses.TerminalEvent;
import openperipheral.addons.glasses.TerminalIdAccess;
import openperipheral.addons.glasses.TerminalUtils;
import openperipheral.addons.glasses.drawable.Drawable;
import openperipheral.addons.glasses.utils.Point2d;
import openperipheral.addons.glasses.utils.RenderState;
import org.lwjgl.opengl.GL11;

public class TerminalManagerClient {

	public static class DrawableHitInfo {
		public final int id;
		public final SurfaceType surfaceType;
		public final float dx;
		public final float dy;
		public final int z;

		public DrawableHitInfo(int id, SurfaceType surfaceType, float dx, float dy, int z) {
			this.id = id;
			this.surfaceType = surfaceType;
			this.dx = dx;
			this.dy = dy;
			this.z = z;
		}
	}

	public static final TerminalManagerClient instance = new TerminalManagerClient();

	private TerminalManagerClient() {}

	private final Table<Long, SurfaceType, SurfaceClient> surfaces = HashBasedTable.create();

	private Optional<Long> terminalGuid = Optional.absent();

	private void tryDrawSurface(long guid, SurfaceType type, float partialTicks, ScaledResolution resolution) {
		SurfaceClient surface = surfaces.get(guid, type);
		if (surface != null) {
			GL11.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_ENABLE_BIT);
			GL11.glShadeModel(GL11.GL_SMOOTH);

			final RenderState renderState = new RenderState();
			renderState.forceKnownState();

			for (Drawable drawable : surface.getSortedDrawables())
				if (drawable.shouldRender()) drawable.draw(resolution, renderState, partialTicks);
			GL11.glPopAttrib();
		}
	}

	public class ForgeBusListener {

		@SubscribeEvent
		public void onRenderGameOverlay(RenderGameOverlayEvent.Pre evt) {
			if (evt.type == ElementType.ALL) {
				GuiScreen gui = FMLClientHandler.instance().getClient().currentScreen;

				if (gui instanceof GuiCapture) {
					final GuiCapture capture = (GuiCapture)gui;
					// this must be here, since there are some elements (like food bar) that are overriden every tick
					capture.forceGuiElementsState();
				}
			}
		}

		@SubscribeEvent
		public void onRenderGameOverlay(RenderGameOverlayEvent.Post evt) {
			if (evt.type == ElementType.HELMET) {
				if (terminalGuid.isPresent()) {
					final long guid = terminalGuid.get();
					tryDrawSurface(guid, SurfaceType.PRIVATE, evt.partialTicks, evt.resolution);
					tryDrawSurface(guid, SurfaceType.GLOBAL, evt.partialTicks, evt.resolution);
				}
			}
		}

		@SubscribeEvent
		public void onItemTooltip(ItemTooltipEvent evt) {
			if (evt.itemStack != null && NbtGuidProviders.hasTerminalCapabilities(evt.itemStack)) {
				final Optional<Long> guid = NbtGuidProviders.getTerminalGuid(evt.itemStack);
				if (guid.isPresent()) {
					evt.toolTip.add(StatCollector.translateToLocalFormatted("openperipheral.terminal.key", TerminalUtils.formatTerminalId(guid.get())));
				} else {
					evt.toolTip.add(StatCollector.translateToLocal("openperipheral.terminal.unbound"));
				}
			}
		}

		private SurfaceClient getOrCreateSurface(TerminalEvent.Data evt) {
			final SurfaceType surfaceType = evt.getSurfaceType();
			SurfaceClient surface = surfaces.get(evt.terminalId, surfaceType);

			if (surface == null) {
				surface = evt.createSurface();
				surfaces.put(evt.terminalId, surfaceType, surface);
			}
			return surface;
		}

		@SubscribeEvent
		public void onTerminalData(TerminalEvent.PrivateDrawableData evt) {
			updateTerminalDrawables(evt);
		}

		@SubscribeEvent
		public void onTerminalData(TerminalEvent.PublicDrawableData evt) {
			updateTerminalDrawables(evt);
		}

		private void updateTerminalDrawables(TerminalEvent.DrawableData evt) {
			final SurfaceClient surface = getOrCreateSurface(evt);
			surface.drawablesContainer.interpretCommandList(evt.commands);
		}

		@SubscribeEvent
		public void onTerminalClear(TerminalEvent.PrivateClear evt) {
			clearTerminal(evt);
		}

		@SubscribeEvent
		public void onTerminalClear(TerminalEvent.PublicClear evt) {
			clearTerminal(evt);
		}

		private void clearTerminal(TerminalEvent.Clear evt) {
			final SurfaceType surfaceType = evt.getSurfaceType();
			surfaces.remove(evt.terminalId, surfaceType);
		}

		@SubscribeEvent
		public void onBackgroundChange(GlassesChangeBackgroundEvent evt) {
			GuiScreen gui = FMLClientHandler.instance().getClient().currentScreen;

			if (gui instanceof GuiCapture) {
				final GuiCapture capture = (GuiCapture)gui;
				long guid = capture.getGuid();
				if (guid == evt.guid) capture.setBackground(evt.backgroundColor);
			}
		}

		@SubscribeEvent
		public void onKeyRepeatSet(GlassesSetKeyRepeatEvent evt) {
			GuiScreen gui = FMLClientHandler.instance().getClient().currentScreen;

			if (gui instanceof GuiCapture) {
				final GuiCapture capture = (GuiCapture)gui;
				long guid = capture.getGuid();
				if (guid == evt.guid) capture.setKeyRepeat(evt.repeat);
			}
		}

		@SubscribeEvent
		public void onDragParamsSet(GlassesSetDragParamsEvent evt) {
			GuiScreen gui = FMLClientHandler.instance().getClient().currentScreen;

			if (gui instanceof GuiCapture) {
				final GuiCapture capture = (GuiCapture)gui;
				long guid = capture.getGuid();
				if (guid == evt.guid) capture.setDragParameters(evt.threshold, evt.period);
			}
		}

		@SubscribeEvent
		public void onGuiVisibilitySet(GlassesSetGuiVisibilityEvent evt) {
			GuiScreen gui = FMLClientHandler.instance().getClient().currentScreen;

			if (gui instanceof GuiCapture) {
				final GuiCapture capture = (GuiCapture)gui;
				long guid = capture.getGuid();
				if (guid == evt.guid) capture.updateGuiElementsState(evt.visibility);
			}
		}

		@SubscribeEvent
		public void onCaptureForce(GlassesStopCaptureEvent evt) {
			GuiScreen gui = FMLClientHandler.instance().getClient().currentScreen;

			if (gui instanceof GuiCapture) {
				long guid = ((GuiCapture)gui).getGuid();
				if (guid == evt.guid) FMLCommonHandler.instance().showGuiScreen(null);
			}
		}
	}

	public Object createForgeBusListener() {
		return new ForgeBusListener();
	}

	public class FmlBusListener {

		@SubscribeEvent
		public void onDisconnect(ClientDisconnectionFromServerEvent evt) {
			surfaces.clear();
		}

		@SubscribeEvent
		public void onClientTick(ClientTickEvent evt) {
			if (evt.phase == Phase.END) {
				final EntityPlayer player = Minecraft.getMinecraft().thePlayer;
				terminalGuid = player != null? TerminalIdAccess.instance.getIdFrom(player) : Optional.<Long> absent();
			}
		}

	}

	public Object createFmlBusListener() {
		return new FmlBusListener();
	}

	public DrawableHitInfo findDrawableHit(long guid, ScaledResolution resolution, float x, float y) {
		DrawableHitInfo result = findDrawableHit(guid, resolution, x, y, SurfaceType.PRIVATE);
		if (result != null) return result;

		return findDrawableHit(guid, resolution, x, y, SurfaceType.GLOBAL);
	}

	private DrawableHitInfo findDrawableHit(long guid, ScaledResolution resolution, float clickX, float clickY, SurfaceType surfaceType) {
		SurfaceClient surface = surfaces.get(guid, surfaceType);

		if (surface == null) return null;

		final int screenWidth = resolution.getScaledWidth();
		final int screenHeight = resolution.getScaledHeight();

		for (Drawable d : Lists.reverse(surface.getSortedDrawables())) {
			if (!d.isClickable()) continue;

			final Box2d bb = d.getBoundingBox();
			final Point2d localClick = d.transformToLocal(clickX, clickY, screenWidth, screenHeight);

			final float localX = localClick.x;
			final float localY = localClick.y;

			if (0 <= localX && 0 <= localY &&
					localX < bb.width && localY < bb.height) { return new DrawableHitInfo(d.getId(), surfaceType, localX, localY, d.z); }
		}

		return null;
	}
}