package ee.futur.easygiantsfoundry;

import static ee.futur.easygiantsfoundry.EasyGiantsFoundryClientIDs.*;
import static ee.futur.easygiantsfoundry.EasyGiantsFoundryHelper.getHeatColor;
import static ee.futur.easygiantsfoundry.EasyGiantsFoundryHelper.renderTextLocation;
import static ee.futur.easygiantsfoundry.MouldHelper.SWORD_TYPE_1_VARBIT;
import static ee.futur.easygiantsfoundry.MouldHelper.SWORD_TYPE_2_VARBIT;
import ee.futur.easygiantsfoundry.enums.CommissionType;
import ee.futur.easygiantsfoundry.enums.FontType;
import ee.futur.easygiantsfoundry.enums.Heat;
import ee.futur.easygiantsfoundry.enums.Stage;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import javax.inject.Inject;

import net.runelite.api.Client;
import net.runelite.api.GameObject;
import net.runelite.api.MenuEntry;
import net.runelite.api.NPC;
import net.runelite.api.Perspective;
import net.runelite.api.Point;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.widgets.Widget;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.outline.ModelOutlineRenderer;
import org.apache.commons.lang3.StringUtils;

public class FoundryOverlay3D extends Overlay
{

	private static final int HAND_IN_WIDGET = 49414221;
	private static final int CRUCIBLE_CAPACITY = 28;
	private final ModelOutlineRenderer modelOutlineRenderer;

	GameObject tripHammer;
	GameObject grindstone;
	GameObject polishingWheel;
	GameObject lavaPool;
	GameObject waterfall;
	GameObject mouldJig;
	GameObject crucible;
	GameObject storage;
	NPC kovac;

	private final Client client;
	private final EasyGiantsFoundryState state;
	private final EasyGiantsFoundryConfig config;

	@Inject
	private FoundryOverlay3D(
		Client client,
		EasyGiantsFoundryState state,
		EasyGiantsFoundryConfig config,
		ModelOutlineRenderer modelOutlineRenderer)
	{
		setPosition(OverlayPosition.DYNAMIC);
		this.client = client;
		this.state = state;
		this.config = config;
		this.modelOutlineRenderer = modelOutlineRenderer;
	}

	private Color getObjectColor(Stage stage, Heat heat)
	{
		if (stage.getHeat() != heat)
		{
			return config.toolBad();
		}

		if (BonusWidget.isActive(client))
		{
			return config.toolBonus();
		}

		int actionsLeft = state.getActionsLeftInStage();
		int heatLeft = state.getActionsForHeatLevel();
		if (actionsLeft <= 1 || heatLeft <= 1)
		{
			return config.toolCaution();
		}

		return config.toolGood();
	}

	private GameObject getStageObject(Stage stage)
	{
		switch (stage)
		{
			case TRIP_HAMMER:
				return tripHammer;
			case GRINDSTONE:
				return grindstone;
			case POLISHING_WHEEL:
				return polishingWheel;
		}
		return null;
	}

	@Override
	public Dimension render(Graphics2D graphics)
	{
		if (!state.isEnabled())
		{
			return null;
		}

		if (config.dynamicOverlayFont() != FontType.DEFAULT)
		{
			graphics.setFont(config.dynamicOverlayFont().getFont());
		}

		if (config.highlightKovac())
		{
			drawKovacIfHandIn(graphics);
		}

		if (client.getVarbitValue(VARBIT_PREFORM_STORED) == 1)
		{
			if (config.highlightStorage())
			{
				drawStorage(graphics);
			}
			return null;
		}

		if (state.getCurrentStage() == null)
		{
			if (config.highlightMould())
			{
				drawMouldIfNotSet(graphics);
			}
			if (config.highlightCrucible())
			{
				drawCrucibleIfMouldSet(graphics);
			}
			if (config.drawMouldInfoOverlay())
			{
				drawMouldScoreIfMouldSet(graphics);
				drawPreformScoreIfPoured(graphics);
			}

			return null;
		}

		Stage stage = state.getCurrentStage();
		GameObject stageObject = getStageObject(stage);
		if (stageObject == null || graphics == null)
		{
			return null;
		}

		Heat heat = state.getCurrentHeat();
		Color color = getObjectColor(stage, heat);
		// TODO Config
		if (config.highlightStyle() == HighlightStyle.HIGHLIGHT_CLICKBOX)
		{
			drawObjectClickbox(graphics, stageObject, color);
		}
		else
		{
			drawObjectOutline(graphics, stageObject, color);
		}

		// !state.heatingCoolingState.isIdle()
		// if the stage heat is already in range, but player still wants to do heat changes
		if ((stage.getHeat() != heat || !state.heatActionStateMachine.isIdle()) && config.highlightWaterAndLava())
		{
			drawHeatChangers(graphics);
		}
		// mouse hover over preview
		else if (config.drawLavaWaterInfoOverlay())
		{
			MenuEntry[] menuEntries = client.getMenuEntries();
			if (menuEntries.length != 0)
			{
				MenuEntry hoveredMenu = menuEntries[menuEntries.length - 1];

				if (hoveredMenu.getIdentifier() == lavaPool.getId())
				{
					drawHeatChangerPreviewOverlay(graphics, lavaPool, true);
				}
				else if (hoveredMenu.getIdentifier() == waterfall.getId())
				{
					drawHeatChangerPreviewOverlay(graphics, waterfall, false);
				}
			}
		}

		drawActionOverlay(graphics, stageObject);

		return null;
	}

	private void drawObjectClickbox(Graphics2D graphics, GameObject stageObject, Color color)
	{
		Shape objectClickbox = stageObject.getClickbox();
		if (objectClickbox != null && config.highlightTools())
		{
			objectClickbox = scaleShape(objectClickbox);
			Point mousePosition = client.getMouseCanvasPosition();
			if (objectClickbox.contains(mousePosition.getX(), mousePosition.getY()))
			{
				graphics.setColor(color.darker());
			}
			else
			{
				graphics.setColor(color);
			}
			graphics.draw(objectClickbox);
			graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20));
			graphics.fill(objectClickbox);
		}
	}

	private void drawObjectOutline(Graphics2D graphics, GameObject stageObject, Color color)
	{
		Color _color = new Color(color.getRed(), color.getGreen(), color.getBlue(), config.borderAlpha());
		modelOutlineRenderer.drawOutline(stageObject, config.borderThickness(), _color, config.borderFeather());
	}

	private void drawHeatChangerPreviewOverlay(
		Graphics2D graphics,
		GameObject stageObject,
		boolean isLava
	)
	{

		HeatActionSolver.DurationResult fastResult =
			HeatActionSolver.solve(
				state.getCurrentStage(),
				state.getCurrentHeatRange(),
				state.getActionsLeftInStage(),
				state.getHeatAmount(),
				true,
				isLava,
				config.heatActionPadTicks(),
				state.isPlayerRunning()
			);

		final int fastDuration = fastResult.getDuration();
		HeatActionSolver.DurationResult slowResult =
			HeatActionSolver.solve(
				state.getCurrentStage(),
				state.getCurrentHeatRange(),
				state.getActionsLeftInStage(),
				state.getHeatAmount(),
				false,
				isLava,
				config.heatActionPadTicks(),
				state.isPlayerRunning()
			);
		final int slowDuration = slowResult.getDuration();

		final String fastName = isLava ? "dunks" : "quenches";
		final String slowName = isLava ? "heats" : "cools";

		String text;
		if (config.debugging())
		{
			text = String.format("%d %s (predicted: %d) or %d %s (predicted: %d) (overshoot: %s goal-in-range: %s)",
				fastDuration, fastName, fastResult.getPredictedHeat(), slowDuration, slowName, slowResult.getPredictedHeat(), slowResult.isOvershooting(), fastResult.isGoalInRange());
		}
		else
		{
			text = String.format("%d %s or %d %s ",
				fastDuration, fastName, slowDuration, slowName);
		}

		LocalPoint stageLoc = stageObject.getLocalLocation();
		stageLoc = new LocalPoint(stageLoc.getX(), stageLoc.getY());

		Point pos = Perspective.getCanvasTextLocation(client, graphics, stageLoc, text, 50);
		if (pos == null)
		{
			return;
		}

		Color color = config.lavaWaterfallColour();

		renderTextLocation(graphics, pos, text, color, config.textBackground(), config.textOutline());
	}

	private void drawHeatChangerOverlay(Graphics2D graphics, GameObject stageObject)
	{

		String text;
		if (config.debugging())
		{
			text = String.format("%d %s (overshoot: %s) [goal-in-range: %s]",
				state.heatActionStateMachine.getRemainingDuration(),
				state.heatActionStateMachine.getActionname(),
				state.heatActionStateMachine.isOverShooting(),
				state.heatActionStateMachine.isGoalInRange()
			);
		}
		else
		{
			text = String.format("%d %s",
				state.heatActionStateMachine.getRemainingDuration(),
				state.heatActionStateMachine.getActionname()
			);
		}

		LocalPoint stageLoc = stageObject.getLocalLocation();
		stageLoc = new LocalPoint(stageLoc.getX(), stageLoc.getY());

		Point pos = Perspective.getCanvasTextLocation(client, graphics, stageLoc, text, 50);
		Color color = config.lavaWaterfallColour();

		renderTextLocation(graphics, pos, text, color, config.textBackground(), config.textOutline());
	}

	private void drawHeatChangers(Graphics2D graphics)
	{
		int change = state.getHeatChangeNeeded();
		Shape shape = null;

		boolean isLava = change > 0;
		boolean isWaterfall = change < 0;
		if (isWaterfall || state.heatActionStateMachine.isCooling())
		{
			shape = waterfall.getClickbox();
		}
		else if (isLava || state.heatActionStateMachine.isHeating())
		{
			shape = lavaPool.getClickbox();
		}
		if (shape != null)
		{
			shape = scaleShape(shape);
			Point mousePosition = client.getMouseCanvasPosition();
			Color color = config.lavaWaterfallColour();
			if (shape.contains(mousePosition.getX(), mousePosition.getY()))
			{
				graphics.setColor(color.darker());
			}
			else
			{
				graphics.setColor(color);
			}
			graphics.draw(shape);
			graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20));
			graphics.fill(shape);
		}

		if (config.drawLavaWaterInfoOverlay())
		{
			if (state.heatActionStateMachine.isCooling())
			{
				drawHeatChangerOverlay(graphics, waterfall);
			}
			else if (isWaterfall)
			{
				drawHeatChangerPreviewOverlay(graphics, waterfall, false);
			}
			if (state.heatActionStateMachine.isHeating())
			{
				drawHeatChangerOverlay(graphics, lavaPool);
			}
			else if (isLava)
			{
				drawHeatChangerPreviewOverlay(graphics, lavaPool, true);
			}
		}
	}


	private void drawCrucibleContent(Graphics2D graphics)
	{
		if (!config.drawCrucibleInfoOverlay())
		{
			return;
		}
		String text = String.format("%d/%d score: %d", state.getCrucibleCount(), CRUCIBLE_CAPACITY, (int)state.getCrucibleScore());

		LocalPoint crucibleLoc = crucible.getLocalLocation();
		crucibleLoc = new LocalPoint(crucibleLoc.getX() - 100, crucibleLoc.getY());

		Point pos = Perspective.getCanvasTextLocation(client, graphics, crucibleLoc, text, 200);
		Color color;
		if (state.getCrucibleCount() == CRUCIBLE_CAPACITY)
		{
			color = config.toolGood();
		}
		else
		{
			color = config.generalHighlight();
		}
		renderTextLocation(graphics, pos, text, color, config.textBackground(), config.textOutline());
	}

	private void drawMouldScoreIfMouldSet(Graphics2D graphics) {
		if (client.getVarbitValue(SWORD_TYPE_1_VARBIT) == 0)
		{
			return;
		}
		if (client.getVarbitValue(VARBIT_GAME_STAGE) != 1)
		{
			return;
		}

		if (state.getMouldScore() < 0)
		{
			return;
		}

		String text = String.format("score: %d", state.getMouldScore());
		LocalPoint mouldLoc = mouldJig.getLocalLocation();
		Point pos = Perspective.getCanvasTextLocation(client, graphics, mouldLoc, text, 115);
		Color color = config.generalHighlight();

		renderTextLocation(graphics, pos, text, color, config.textBackground(), config.textOutline());
	}
	private void drawPreformScoreIfPoured(Graphics2D graphics) {
		if (client.getVarbitValue(VARBIT_GAME_STAGE) != 2)
		{
			return;
		}

		if (state.getMouldScore() < 0 || state.getLastKnownCrucibleScore() < 0)
		{
			return;
		}

		int preformScore = state.getLastKnownCrucibleScore() + state.getMouldScore();
		String text = String.format("score: %d", preformScore);
		LocalPoint mouldLoc = mouldJig.getLocalLocation();
		Point pos = Perspective.getCanvasTextLocation(client, graphics, mouldLoc, text, 115);

		Color color = config.generalHighlight();

		renderTextLocation(graphics, pos, text, color, config.textBackground(), config.textOutline());
	}

	private void drawCrucibleIfMouldSet(Graphics2D graphics)
	{
		if (client.getVarbitValue(SWORD_TYPE_1_VARBIT) == 0)
		{
			return;
		}
		if (client.getVarbitValue(VARBIT_GAME_STAGE) != 1)
		{
			return;
		}

		if (config.highlightStyle() == HighlightStyle.HIGHLIGHT_CLICKBOX)
		{
			Shape shape = crucible.getConvexHull();
			if (shape != null)
			{
				shape = scaleShape(shape);
				Color color = config.generalHighlight();
				if (state.getCrucibleCount() == CRUCIBLE_CAPACITY)
				{
					graphics.setColor(config.toolGood());
				}
				else
				{
					graphics.setColor(config.generalHighlight());
				}
				graphics.draw(shape);
				graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20));
				graphics.fill(shape);
			}
		}
		else if (config.highlightStyle() == HighlightStyle.HIGHLIGHT_BORDER)
		{
			Color color;
			if (state.getCrucibleCount() == CRUCIBLE_CAPACITY)
			{
				color = config.toolGood();
			}
			else
			{
				color = config.generalHighlight();
			}
			drawObjectOutline(graphics, crucible, color);
		}

		drawCrucibleContent(graphics);
	}

	private void drawMouldIfNotSet(Graphics2D graphics)
	{
		if (client.getWidget(WIDGET_PROGRESS_PARENT) != null
			|| client.getVarbitValue(SWORD_TYPE_1_VARBIT) == 0
			|| (client.getVarbitValue(VARBIT_GAME_STAGE) != 0
			&& client.getVarbitValue(VARBIT_GAME_STAGE) != 2))
		{
			return;
		}
		if (config.highlightStyle() == HighlightStyle.HIGHLIGHT_CLICKBOX)
		{
			Shape shape = mouldJig.getConvexHull();
			if (shape != null)
			{
				shape = scaleShape(shape);
				Color color = config.generalHighlight();
				graphics.setColor(color);
				graphics.draw(shape);
				graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20));
				graphics.fill(shape);
			}
		}
		else if (config.highlightStyle() == HighlightStyle.HIGHLIGHT_BORDER)
		{
			drawObjectOutline(graphics, mouldJig, config.generalHighlight());
		}

		if (config.drawMouldInfoOverlay())
		{
			CommissionType type1 = CommissionType.forVarbit(client.getVarbitValue(SWORD_TYPE_1_VARBIT));
			CommissionType type2 = CommissionType.forVarbit(client.getVarbitValue(SWORD_TYPE_2_VARBIT));
			String text = StringUtils.capitalize(type1.toString().toLowerCase()) + " " + StringUtils.capitalize(type2.toString().toLowerCase());
			LocalPoint textLocation = mouldJig.getLocalLocation();
			textLocation = new LocalPoint(textLocation.getX(), textLocation.getY());
			Point canvasLocation = Perspective.getCanvasTextLocation(client, graphics, textLocation, text, 100);
			canvasLocation = new Point(canvasLocation.getX(), canvasLocation.getY() + 10);
			renderTextLocation(graphics, canvasLocation, text, config.generalHighlight(), config.textBackground(), config.textOutline());
		}
	}

	private Shape scaleShape(Shape shape)
	{
		if (shape == null)
		{
			return null;
		}

		double scale = config.clickboxScale() / 100.0;
		if (Math.abs(scale - 1.0) < 0.01)
		{
			return shape;
		}

		Rectangle2D bounds = shape.getBounds2D();
		AffineTransform transform = new AffineTransform();
		transform.translate(bounds.getCenterX(), bounds.getCenterY());
		transform.scale(scale, scale);
		transform.translate(-bounds.getCenterX(), -bounds.getCenterY());
		return transform.createTransformedShape(shape);
	}

	private void drawStorage(Graphics2D graphics)
	{
		Shape shape = storage.getConvexHull();
		if (shape != null)
		{
			shape = scaleShape(shape);
			Color color = config.generalHighlight();
			graphics.setColor(color);
			graphics.draw(shape);
			graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20));
			graphics.fill(shape);
		}
	}

	private void drawKovacIfHandIn(Graphics2D graphics)
	{
		Widget handInWidget = client.getWidget(HAND_IN_WIDGET);
		if (handInWidget != null && !handInWidget.isHidden())
		{
		Shape shape = kovac.getConvexHull();
		if (shape != null)
		{
			shape = scaleShape(shape);
			Color color = config.generalHighlight();
			graphics.setColor(color);
			graphics.draw(shape);
			graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 20));
			graphics.fill(shape);
			}
		}
	}

	private void drawActionOverlay(Graphics2D graphics, GameObject gameObject)
	{


		int actionsLeft = state.getActionsLeftInStage();
		int heatLeft = state.getActionsForHeatLevel();

		// Draw heat left
		if (config.drawHeatLeftOverlay())
		{
			String text = "Heat left: " + heatLeft;
			LocalPoint textLocation = gameObject.getLocalLocation();
			textLocation = new LocalPoint(textLocation.getX(), textLocation.getY());
			Point canvasLocation = Perspective.getCanvasTextLocation(client, graphics, textLocation, text, 250);
			if (canvasLocation == null)
			{
				return;
			}
			renderTextLocation(graphics, canvasLocation, text, getHeatColor(actionsLeft, heatLeft), config.textBackground(), config.textOutline());
		}
		if (config.drawActionLeftOverlay())
		// Draw actions left
		{
			String text = "Actions left: " + actionsLeft;
			LocalPoint textLocation = gameObject.getLocalLocation();
			textLocation = new LocalPoint(textLocation.getX(), textLocation.getY());
			Point canvasLocation = Perspective.getCanvasTextLocation(client, graphics, textLocation, text, 250);
			if (canvasLocation == null)
			{
				return;
			}
			canvasLocation = new Point(canvasLocation.getX(), canvasLocation.getY() + graphics.getFontMetrics().getHeight());
			renderTextLocation(graphics, canvasLocation, text, getHeatColor(actionsLeft, heatLeft), config.textBackground(), config.textOutline());
		}
	}
}
