package com.baselet.diagram.draw.swing;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D;

import com.baselet.control.StringStyle;
import com.baselet.control.basics.Converter;
import com.baselet.control.basics.geom.DimensionDouble;
import com.baselet.control.basics.geom.PointDouble;
import com.baselet.control.constants.Constants;
import com.baselet.control.enums.AlignHorizontal;
import com.baselet.control.util.Utils;
import com.baselet.diagram.DiagramHandler;
import com.baselet.diagram.draw.DrawFunction;
import com.baselet.diagram.draw.DrawHandler;
import com.baselet.diagram.draw.helper.ColorOwn;
import com.baselet.diagram.draw.helper.Style;
import com.baselet.diagram.draw.swing.javascriptparser.JavascriptParserSwing;
import com.baselet.element.interfaces.GridElement;

public class DrawHandlerSwing extends DrawHandler {

	private Graphics2D g2;

	protected DiagramHandler handler;

	private boolean translate; // is used because pdf and svg export cut lines if they are drawn at (0,0)

	private final GridElement gridElement;

	public DrawHandlerSwing(GridElement gridElement) {
		super();
		this.gridElement = gridElement;
		javascriptCodeParser = new JavascriptParserSwing(this);
	}

	/**
	 * Java Swing JComponents have a width of w, but only w-1 pixels are drawable
	 * Therefore to draw a rectangle around the whole element, you must call g.drawRect(0,0,w-1,h-1)
	 * To avoid this displacement pixel at every draw method, this method ensures you can never draw outside of the right component border
	 * x is also important for width drawings which don't start at 0 (e.g. Deployment "3-dimensional" Rectangle)
	 */
	private double inBorderHorizontal(double width, double x) {
		return Math.min(gridElement.getRectangle().getWidth() - x - 1, width);
	}

	/**
	 * same as above but for vertical points
	 */
	private double inBorderVertical(double height, double y) {
		return Math.min(gridElement.getRectangle().getHeight() - y - 1, height);
	}

	public void setHandler(DiagramHandler handler) {
		this.handler = handler;
		style = new Style();
		resetStyle();
	}

	public void setGraphics(Graphics g) {
		g2 = (Graphics2D) g;
	}

	private double getZoom() {
		return handler.getZoomFactor();
	}

	@Override
	public DimensionDouble textDimensionHelper(StringStyle singleLine) {
		boolean specialFontSize = style.getFontSize() != getDefaultFontSize();
		if (specialFontSize) {
			handler.getFontHandler().setFontSize(style.getFontSize());
		}
		DimensionDouble returnVal = handler.getFontHandler().getTextSize(singleLine, false);
		if (specialFontSize) {
			handler.getFontHandler().resetFontSize();
		}
		return returnVal;
	}

	@Override
	public double getDefaultFontSize() {
		return handler.getFontHandler().getFontSize(false);
	}

	/* DRAW METHODS */
	@Override
	public void drawArc(double x, double y, double width, double height, double start, double extent, boolean open) {
		double xZoomed = x * getZoom() + HALF_PX;
		double yZoomed = y * getZoom() + HALF_PX;
		int arcType = open ? Arc2D.OPEN : Arc2D.PIE;
		addShape(new Arc2D.Double(xZoomed, yZoomed, inBorderHorizontal(width * getZoom(), xZoomed), inBorderVertical(height * getZoom(), yZoomed), start, extent, arcType));
	}

	@Override
	public void drawCircle(double x, double y, double radius) {
		double widthAndHeight = radius * 2;
		drawEllipse(x - radius, y - radius, widthAndHeight, widthAndHeight);
	}

	@Override
	public void drawEllipse(double x, double y, double width, double height) {
		double xZoomed = x * getZoom() + HALF_PX;
		double yZoomed = y * getZoom() + HALF_PX;
		addShape(new Ellipse2D.Double(xZoomed, yZoomed, inBorderHorizontal(width * getZoom(), xZoomed), inBorderVertical(height * getZoom(), yZoomed)));
	}

	@Override
	public void drawLines(PointDouble... points) {
		if (points.length > 0) {
			Path2D.Double path = new Path2D.Double();
			boolean first = true;
			boolean fillShape = false;
			int lastIdx = points.length - 1;
			for (int i = 0; i < points.length; i++) {
				PointDouble p = points[i];
				Double x = inBorderHorizontal(Double.valueOf(p.getX() * getZoom() + HALF_PX), 0);
				Double y = inBorderVertical(Double.valueOf(p.getY() * getZoom() + HALF_PX), 0);
				if (first) {
					path.moveTo(x, y);
					first = false;
				}
				// if this is the last point AND the first and last points are equal, close the path and fill the shape
				else if (i == lastIdx && points[0].equals(points[lastIdx])) {
					path.closePath();
					fillShape = true;
				}
				else {
					path.lineTo(x, y);
				}
			}
			addShape(path, fillShape);
		}
	}

	@Override
	public void drawRectangle(double x, double y, double width, double height) {
		double xZoomed = x * getZoom() + HALF_PX;
		double yZoomed = y * getZoom() + HALF_PX;
		addShape(new Rectangle.Double(xZoomed, yZoomed, inBorderHorizontal(width * getZoom(), xZoomed), inBorderVertical(height * getZoom(), yZoomed)));
	}

	@Override
	public void drawRectangleRound(double x, double y, double width, double height, double radius) {
		double rad = radius * 2 * getZoom();
		double xZoomed = x * getZoom() + HALF_PX;
		double yZoomed = y * getZoom() + HALF_PX;
		addShape(new RoundRectangle2D.Double(xZoomed, yZoomed, inBorderHorizontal(width * getZoom(), xZoomed), inBorderVertical(height * getZoom(), yZoomed), rad, rad));
	}

	@Override
	public void printHelper(StringStyle[] text, PointDouble point, AlignHorizontal align) {
		addText(new Text(text, point.x * getZoom(), point.y * getZoom(), align));
	}

	protected void addShape(final Shape s) {
		addShape(s, true);
	}

	protected void addShape(final Shape s, final boolean fillShape) {
		final Style styleAtDrawingCall = style.cloneFromMe();
		addDrawable(new DrawFunction() {
			@Override
			public void run() {
				drawShape(styleAtDrawingCall, s, fillShape);
			}
		});
	}

	private void drawShape(Style style, Shape s, boolean fillShape) {
		if (fillShape) {
			// Shapes Background
			g2.setColor(Converter.convert(style.getBackgroundColor()));
			g2.fill(s);
		}
		if (translate) {
			double xTranslation = s.getBounds().x == 0 ? Constants.EXPORT_DISPLACEMENT : 0;
			double yTranslation = s.getBounds().y == 0 ? Constants.EXPORT_DISPLACEMENT : 0;
			g2.translate(xTranslation, yTranslation);
		}
		if (style.getLineWidth() > 0) {
			// Shapes Foreground
			ColorOwn colOwn = getOverlay().getForegroundColor() != null ? getOverlay().getForegroundColor() : style.getForegroundColor();
			g2.setColor(Converter.convert(colOwn));
			g2.setStroke(Utils.getStroke(style.getLineType(), (float) style.getLineWidth()));
			g2.draw(s);
		}
	}

	protected void addText(final Text t) {
		final Style styleAtDrawingCall = style.cloneFromMe();
		addDrawable(new DrawFunction() {
			@Override
			public void run() {
				drawText(styleAtDrawingCall, t);
			}
		});
	}

	private void drawText(Style style, Text t) {
		ColorOwn col = getOverlay().getForegroundColor() != null ? getOverlay().getForegroundColor() : style.getForegroundColor();
		g2.setColor(Converter.convert(col));
		handler.getFontHandler().setFontSize(style.getFontSize());
		g2.setFont(handler.getFontHandler().getFont());
		handler.getFontHandler().writeText(g2, t.getText(), t.getX(), t.getY(), t.getHorizontalAlignment());
		handler.getFontHandler().resetFontSize();
	}

	public void setTranslate(boolean translate) {
		this.translate = translate;
	}
}
