package com.kreative.paint.material.stroke;

import java.awt.BasicStroke;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.util.Arrays;

public class PowerStroke implements Stroke {
	public static final PowerStroke DEFAULT = new PowerStroke(
		/* lineWidth    = */ 1.0f,
		/* multiplicity = */ 1,
		/* dashArray    = */ null,
		/* dashPhase    = */ 0.0f,
		/* arrowOnStart = */ null,
		/* arrowOnEnd   = */ null,
		/* endCap       = */ EndCap.SQUARE,
		/* lineJoin     = */ LineJoin.MITER,
		/* miterLimit   = */ 10.0f,
		/* name         = */ "Default"
	);
	
	private final BasicStroke base;
	public final float lineWidth;
	public final int multiplicity;
	public final float[] dashArray;
	public final float dashPhase;
	public final Arrowhead arrowOnStart;
	public final Arrowhead arrowOnEnd;
	public final EndCap endCap;
	public final LineJoin lineJoin;
	public final float miterLimit;
	public final String name;
	
	public PowerStroke(
		float lineWidth, int multiplicity,
		float[] dashArray, float dashPhase,
		Arrowhead arrowOnStart, Arrowhead arrowOnEnd,
		EndCap endCap, LineJoin lineJoin, float miterLimit,
		String name
	) {
		this.base = new BasicStroke(
			lineWidth,
			((endCap != null) ? endCap.awtValue : 0),
			((lineJoin != null) ? lineJoin.awtValue : 0),
			miterLimit,
			dashArray,
			dashPhase
		);
		this.lineWidth = lineWidth;
		this.multiplicity = multiplicity;
		this.dashArray = dashArray;
		this.dashPhase = dashPhase;
		this.arrowOnStart = arrowOnStart;
		this.arrowOnEnd = arrowOnEnd;
		this.endCap = endCap;
		this.lineJoin = lineJoin;
		this.miterLimit = miterLimit;
		this.name = name;
	}
	
	public Shape createStrokedShape(Shape shape) {
		Shape originalShape = shape;
		Shape strokedShape = shape;
		for (int i = 0; i < multiplicity; i++) {
			strokedShape = base.createStrokedShape(strokedShape);
		}
		if (arrowOnStart == null && arrowOnEnd == null) {
			return strokedShape;
		}
		float sx1 = 0.0f;
		float sy1 = 0.0f;
		float sx2 = 0.0f;
		float sy2 = 0.0f;
		boolean startset = false;
		float ex1 = 0.0f;
		float ey1 = 0.0f;
		float ex2 = 0.0f;
		float ey2 = 0.0f;
		boolean endset = false;
		float mx = 0.0f;
		float my = 0.0f;
		float cx = 0.0f;
		float cy = 0.0f;
		float[] coords = new float[6];
		PathIterator i = originalShape.getPathIterator(null);
		while (!i.isDone()) {
			switch (i.currentSegment(coords)) {
			case PathIterator.SEG_MOVETO:
				mx = coords[0]; my = coords[1];
				cx = coords[0]; cy = coords[1];
				break;
			case PathIterator.SEG_LINETO:
				if (!startset) {
					sx1 = cx; sy1 = cy;
					sx2 = coords[0]; sy2 = coords[1];
					startset = true;
				}
				ex1 = cx; ey1 = cy;
				ex2 = coords[0]; ey2 = coords[1];
				endset = true;
				cx = coords[0]; cy = coords[1];
				break;
			case PathIterator.SEG_QUADTO:
				if (!startset) {
					sx1 = cx; sy1 = cy;
					sx2 = coords[0]; sy2 = coords[1];
					startset = true;
				}
				ex1 = coords[0]; ey1 = coords[1];
				ex2 = coords[2]; ey2 = coords[3];
				endset = true;
				cx = coords[2]; cy = coords[3];
				break;
			case PathIterator.SEG_CUBICTO:
				if (!startset) {
					sx1 = cx; sy1 = cy;
					sx2 = coords[0]; sy2 = coords[1];
					startset = true;
				}
				ex1 = coords[2]; ey1 = coords[3];
				ex2 = coords[4]; ey2 = coords[5];
				endset = true;
				cx = coords[4]; cy = coords[5];
				break;
			case PathIterator.SEG_CLOSE:
				if (!startset) {
					sx1 = cx; sy1 = cy;
					sx2 = mx; sy2 = my;
					startset = true;
				}
				ex1 = cx; ey1 = cy;
				ex2 = mx; ey2 = my;
				endset = true;
				cx = mx; cy = my;
				break;
			}
			i.next();
		}
		if (startset && endset && sx1 == ex2 && sy1 == ey2) {
			return strokedShape;
		}
		Area a = new Area(strokedShape);
		if (startset && arrowOnStart != null) {
			Shape arrow = arrowOnStart.createArrowhead(sx2, sy2, sx1, sy1, lineWidth);
			a.add(new Area(arrow));
		}
		if (endset && arrowOnEnd != null) {
			Shape arrow = arrowOnEnd.createArrowhead(ex1, ey1, ex2, ey2, lineWidth);
			a.add(new Area(arrow));
		}
		return a;
	}
	
	@Override
	public boolean equals(Object that) {
		if (that instanceof PowerStroke) {
			return this.equals((PowerStroke)that, false);
		} else {
			return false;
		}
	}
	
	public boolean equals(PowerStroke that, boolean withName) {
		if (this.lineWidth != that.lineWidth) return false;
		if (this.multiplicity != that.multiplicity) return false;
		if (!Arrays.equals(this.dashArray, that.dashArray)) return false;
		if (this.dashPhase != that.dashPhase) return false;
		if (!arrowheadEquals(this.arrowOnStart, that.arrowOnStart)) return false;
		if (!arrowheadEquals(this.arrowOnEnd, that.arrowOnEnd)) return false;
		if (this.endCap != that.endCap) return false;
		if (this.lineJoin != that.lineJoin) return false;
		if (this.miterLimit != that.miterLimit) return false;
		if (!withName) return true;
		if (this.name != null) return this.name.equals(that.name);
		if (that.name != null) return that.name.equals(this.name);
		return true;
	}
	
	@Override
	public int hashCode() {
		int hashCode = 0;
		hashCode ^= Float.floatToIntBits(lineWidth);
		hashCode ^= multiplicity;
		hashCode ^= Arrays.hashCode(dashArray);
		hashCode ^= Float.floatToIntBits(dashPhase);
		if (arrowOnStart != null) hashCode ^= arrowOnStart.hashCode();
		if (arrowOnEnd != null) hashCode ^= arrowOnEnd.hashCode();
		if (endCap != null) hashCode ^= endCap.awtValue;
		if (lineJoin != null) hashCode ^= lineJoin.awtValue;
		hashCode ^= Float.floatToIntBits(miterLimit);
		return hashCode;
	}
	
	public PowerStroke deriveBasicStroke(BasicStroke stroke) {
		return new PowerStroke(
			stroke.getLineWidth(), multiplicity,
			stroke.getDashArray(), stroke.getDashPhase(),
			arrowOnStart, arrowOnEnd,
			EndCap.forAWTValue(stroke.getEndCap()),
			LineJoin.forAWTValue(stroke.getLineJoin()),
			stroke.getMiterLimit(),
			null
		);
	}
	public PowerStroke deriveLineWidth(float lineWidth) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveMultiplicity(int multiplicity) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveDashArray(float[] dashArray) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveDashPhase(float dashPhase) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveArrowOnStart(Arrowhead arrowOnStart) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveArrowOnEnd(Arrowhead arrowOnEnd) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveEndCap(EndCap endCap) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveLineJoin(LineJoin lineJoin) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveMiterLimit(float miterLimit) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveName(String name) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			name
		);
	}
	
	private static boolean arrowheadEquals(Arrowhead a, Arrowhead b) {
		if (a == null) return (b == null);
		if (b == null) return (a == null);
		return a.equals(b);
	}
}

/* endCap       = */
package com.kreative.paint.material.stroke;

import java.awt.BasicStroke;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.util.Arrays;

public class PowerStroke implements Stroke {
	public static final PowerStroke DEFAULT = new PowerStroke(
		/* lineWidth    = */ 1.0f,
		/* multiplicity = */ 1,
		/* dashArray    = */ null,
		/* dashPhase    = */ 0.0f,
		/* arrowOnStart = */ null,
		/* arrowOnEnd   = */ null,
		/* endCap  <SUF>*/ EndCap.SQUARE,
		/* lineJoin     = */ LineJoin.MITER,
		/* miterLimit   = */ 10.0f,
		/* name         = */ "Default"
	);
	
	private final BasicStroke base;
	public final float lineWidth;
	public final int multiplicity;
	public final float[] dashArray;
	public final float dashPhase;
	public final Arrowhead arrowOnStart;
	public final Arrowhead arrowOnEnd;
	public final EndCap endCap;
	public final LineJoin lineJoin;
	public final float miterLimit;
	public final String name;
	
	public PowerStroke(
		float lineWidth, int multiplicity,
		float[] dashArray, float dashPhase,
		Arrowhead arrowOnStart, Arrowhead arrowOnEnd,
		EndCap endCap, LineJoin lineJoin, float miterLimit,
		String name
	) {
		this.base = new BasicStroke(
			lineWidth,
			((endCap != null) ? endCap.awtValue : 0),
			((lineJoin != null) ? lineJoin.awtValue : 0),
			miterLimit,
			dashArray,
			dashPhase
		);
		this.lineWidth = lineWidth;
		this.multiplicity = multiplicity;
		this.dashArray = dashArray;
		this.dashPhase = dashPhase;
		this.arrowOnStart = arrowOnStart;
		this.arrowOnEnd = arrowOnEnd;
		this.endCap = endCap;
		this.lineJoin = lineJoin;
		this.miterLimit = miterLimit;
		this.name = name;
	}
	
	public Shape createStrokedShape(Shape shape) {
		Shape originalShape = shape;
		Shape strokedShape = shape;
		for (int i = 0; i < multiplicity; i++) {
			strokedShape = base.createStrokedShape(strokedShape);
		}
		if (arrowOnStart == null && arrowOnEnd == null) {
			return strokedShape;
		}
		float sx1 = 0.0f;
		float sy1 = 0.0f;
		float sx2 = 0.0f;
		float sy2 = 0.0f;
		boolean startset = false;
		float ex1 = 0.0f;
		float ey1 = 0.0f;
		float ex2 = 0.0f;
		float ey2 = 0.0f;
		boolean endset = false;
		float mx = 0.0f;
		float my = 0.0f;
		float cx = 0.0f;
		float cy = 0.0f;
		float[] coords = new float[6];
		PathIterator i = originalShape.getPathIterator(null);
		while (!i.isDone()) {
			switch (i.currentSegment(coords)) {
			case PathIterator.SEG_MOVETO:
				mx = coords[0]; my = coords[1];
				cx = coords[0]; cy = coords[1];
				break;
			case PathIterator.SEG_LINETO:
				if (!startset) {
					sx1 = cx; sy1 = cy;
					sx2 = coords[0]; sy2 = coords[1];
					startset = true;
				}
				ex1 = cx; ey1 = cy;
				ex2 = coords[0]; ey2 = coords[1];
				endset = true;
				cx = coords[0]; cy = coords[1];
				break;
			case PathIterator.SEG_QUADTO:
				if (!startset) {
					sx1 = cx; sy1 = cy;
					sx2 = coords[0]; sy2 = coords[1];
					startset = true;
				}
				ex1 = coords[0]; ey1 = coords[1];
				ex2 = coords[2]; ey2 = coords[3];
				endset = true;
				cx = coords[2]; cy = coords[3];
				break;
			case PathIterator.SEG_CUBICTO:
				if (!startset) {
					sx1 = cx; sy1 = cy;
					sx2 = coords[0]; sy2 = coords[1];
					startset = true;
				}
				ex1 = coords[2]; ey1 = coords[3];
				ex2 = coords[4]; ey2 = coords[5];
				endset = true;
				cx = coords[4]; cy = coords[5];
				break;
			case PathIterator.SEG_CLOSE:
				if (!startset) {
					sx1 = cx; sy1 = cy;
					sx2 = mx; sy2 = my;
					startset = true;
				}
				ex1 = cx; ey1 = cy;
				ex2 = mx; ey2 = my;
				endset = true;
				cx = mx; cy = my;
				break;
			}
			i.next();
		}
		if (startset && endset && sx1 == ex2 && sy1 == ey2) {
			return strokedShape;
		}
		Area a = new Area(strokedShape);
		if (startset && arrowOnStart != null) {
			Shape arrow = arrowOnStart.createArrowhead(sx2, sy2, sx1, sy1, lineWidth);
			a.add(new Area(arrow));
		}
		if (endset && arrowOnEnd != null) {
			Shape arrow = arrowOnEnd.createArrowhead(ex1, ey1, ex2, ey2, lineWidth);
			a.add(new Area(arrow));
		}
		return a;
	}
	
	@Override
	public boolean equals(Object that) {
		if (that instanceof PowerStroke) {
			return this.equals((PowerStroke)that, false);
		} else {
			return false;
		}
	}
	
	public boolean equals(PowerStroke that, boolean withName) {
		if (this.lineWidth != that.lineWidth) return false;
		if (this.multiplicity != that.multiplicity) return false;
		if (!Arrays.equals(this.dashArray, that.dashArray)) return false;
		if (this.dashPhase != that.dashPhase) return false;
		if (!arrowheadEquals(this.arrowOnStart, that.arrowOnStart)) return false;
		if (!arrowheadEquals(this.arrowOnEnd, that.arrowOnEnd)) return false;
		if (this.endCap != that.endCap) return false;
		if (this.lineJoin != that.lineJoin) return false;
		if (this.miterLimit != that.miterLimit) return false;
		if (!withName) return true;
		if (this.name != null) return this.name.equals(that.name);
		if (that.name != null) return that.name.equals(this.name);
		return true;
	}
	
	@Override
	public int hashCode() {
		int hashCode = 0;
		hashCode ^= Float.floatToIntBits(lineWidth);
		hashCode ^= multiplicity;
		hashCode ^= Arrays.hashCode(dashArray);
		hashCode ^= Float.floatToIntBits(dashPhase);
		if (arrowOnStart != null) hashCode ^= arrowOnStart.hashCode();
		if (arrowOnEnd != null) hashCode ^= arrowOnEnd.hashCode();
		if (endCap != null) hashCode ^= endCap.awtValue;
		if (lineJoin != null) hashCode ^= lineJoin.awtValue;
		hashCode ^= Float.floatToIntBits(miterLimit);
		return hashCode;
	}
	
	public PowerStroke deriveBasicStroke(BasicStroke stroke) {
		return new PowerStroke(
			stroke.getLineWidth(), multiplicity,
			stroke.getDashArray(), stroke.getDashPhase(),
			arrowOnStart, arrowOnEnd,
			EndCap.forAWTValue(stroke.getEndCap()),
			LineJoin.forAWTValue(stroke.getLineJoin()),
			stroke.getMiterLimit(),
			null
		);
	}
	public PowerStroke deriveLineWidth(float lineWidth) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveMultiplicity(int multiplicity) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveDashArray(float[] dashArray) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveDashPhase(float dashPhase) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveArrowOnStart(Arrowhead arrowOnStart) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveArrowOnEnd(Arrowhead arrowOnEnd) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveEndCap(EndCap endCap) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveLineJoin(LineJoin lineJoin) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveMiterLimit(float miterLimit) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			null
		);
	}
	public PowerStroke deriveName(String name) {
		return new PowerStroke(
			lineWidth, multiplicity,
			dashArray, dashPhase,
			arrowOnStart, arrowOnEnd,
			endCap, lineJoin, miterLimit,
			name
		);
	}
	
	private static boolean arrowheadEquals(Arrowhead a, Arrowhead b) {
		if (a == null) return (b == null);
		if (b == null) return (a == null);
		return a.equals(b);
	}
}
