/* 
 
Copyright 2006 Rene Grothmann, modified by Eric Hakenholz

This file is part of C.a.R. software.

    C.a.R. is a free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, version 3 of the License.

    C.a.R. is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 
 
 package rene.zirkel.graphics;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.ImageObserver;
import java.io.FileOutputStream;
import java.io.PrintWriter;

import rene.gui.Global;
import rene.zirkel.ZirkelFrame;
import rene.zirkel.construction.Construction;
import rene.zirkel.objects.ConstructionObject;
import rene.zirkel.objects.PointObject;

class PdfFontMetrics extends FontMetrics {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	int Size;

	public PdfFontMetrics(final int size) {
		super(new Font("Courier", Font.PLAIN, size)); // a dummy font.
		Size = size;
	}

	@Override
	public int stringWidth(final String s) {
		return s.length() * Size;
	}

	@Override
	public int getHeight() {
		return Size;
	}

	@Override
	public int getAscent() {
		return Size * 4 / 5;
	}

}

public class MyGraphicsPDF extends MyGraphics {
	PrintWriter Out;
	StringBuffer buf, xref, stream;
	int nxref = 1;
	String prefix = "";
	final int Normal = 0, Thin = 1, Thick = 2;
	int Stroke = Normal;
	private PdfFontMetrics FM = new PdfFontMetrics(10);
	boolean Bold;
	double W, H;
	double LineWidth;

	public MyGraphicsPDF(final PrintWriter out, final double w, final double h,
			final double linewidth) {
		Out = out;
		LineWidth = linewidth;
		W = w;
		H = h;
		buf = new StringBuffer();
		xref = new StringBuffer();
		appendxref(0, 65535, false);
		appendln("%PDF-1.4");
		newObject();
		appendln("<< /Type /Catalog");
		appendln("   /Outlines 2 0 R");
		appendln("   /Pages 3 0 R");
		appendln(">>");
		endObject();
		newObject();
		appendln("<< /Type /Outlines");
		appendln("   /Count 0");
		appendln(">>");
		endObject();
		newObject();
		appendln("<< /Type /Pages");
		appendln("   /Kids [4 0 R]");
		appendln("   /Count 1");
		appendln(">>");
		endObject();
		newObject();
		appendln("<< /Type /Page");
		appendln("   /Parent 3 0 R");
		appendln("   /MediaBox [0 0 " + w + " " + h + "]");
		appendln("   /Contents 5 0 R");
		appendln("   /Resources << /ProcSet 6 0 R");
		appendln("                 /Font << /F1 7 0 R");
		appendln("                          /F2 7 0 R "
				+ "								/F3 7 0 R >>");
		appendln("              >>");
		appendln(">>");
		endObject();
		newObject();
		stream = new StringBuffer();
	}

	public void close() {
		appendln("<< /Length " + stream.length() + " >>");
		prefix = "";
		appendln("stream");
		buf.append(stream.toString());
		appendln("endstream");
		endObject();
		newObject();
		appendln("[/PDF]");
		endObject();
		newObject();
		appendln("<< /Type /Font");
		appendln("   /Subtype /Type1");
		appendln("   /Name /F1");
		appendln("   /BaseFont /Helvetica");
		appendln("   /Encoding /WinAnsiEncoding");
		appendln(">>");
		endObject();
		newObject();
		appendln("<< /Type /Font");
		appendln("   /Subtype /Type1");
		appendln("   /Name /F2");
		appendln("   /BaseFont /Symbol");
		appendln("   /Encoding /WinAnsiEncoding");
		appendln(">>");
		endObject();
		newObject();
		appendln("<< /Type /Font");
		appendln("   /Subtype /Type1");
		appendln("   /Name /F3");
		appendln("   /BaseFont /Helvetica-Bold");
		appendln("   /Encoding /WinAnsiEncoding");
		appendln(">>");
		endObject();
		appendln("");
		appendln("xref");
		final int xrefstart = buf.length();
		appendln(0 + " " + nxref);
		buf.append(xref.toString());
		appendln("");
		appendln("trailer");
		appendln("<< /Size " + nxref);
		appendln("   /Root 1 0 R");
		appendln(">>");
		appendln("");
		appendln("startxref");
		appendln("" + xrefstart);
		appendln("");
		appendln("%%EOF");
		Out.write(buf.toString());
	}

	public void appendln(final String s) {
		buf.append(prefix + s);
		buf.append((char) 13);
		buf.append((char) 10);
	}

	public void streamln(final String s) {
		stream.append(prefix + s);
		stream.append((char) 13);
		stream.append((char) 10);
	}

	public void appendxref(final int offset, final int number,
			final boolean present) {
		xref.append(format(offset, 10) + " " + format(number, 5)
				+ (present ? " n" : " f"));
		xref.append((char) 13);
		xref.append((char) 10);
	}

	public String format(final int n, final int size) {
		String s = "" + n;
		while (s.length() < size)
			s = "0" + s;
		return s;
	}

	public void newObject() {
		final int n = buf.length();
		appendln("");
		appendln(nxref + " " + 0 + " obj");
		appendxref(n, 0, true);
		nxref++;
		prefix = "  ";
	}

	public void endObject() {
		prefix = "";
		appendln("endobj");
	}

	public double r(final double x) {
		return Math.round(x * 100.0) / 100.0;
	}

	public double ry(final double y) {
		return Math.round((H - y) * 100.0) / 100.0;
	}

	public void setStroke(final int stroke) {
		if (Stroke == stroke)
			return;
		Stroke = stroke;
		switch (Stroke) {
		case Normal:
			streamln(r(LineWidth) + " w");
			streamln("[] 0 d");
			break;
		case Thick:
			streamln(r(3 * LineWidth) + " w");
			streamln("[] 0 d");
			break;
		case Thin:
			streamln(LineWidth + " w");
			streamln("[" + r(3 * LineWidth) + " " + r(3 * LineWidth) + "] 0 d");
			break;
		}

	}

	@Override
	public void clearRect(final int x, final int y, final int w, final int h,
			final Color c) {
	}

	Color OldColor = null;

	@Override
	public void setColor(final Color c) {
		if (OldColor != null && c.getRed() == OldColor.getRed()
				&& c.getGreen() == OldColor.getGreen()
				&& c.getBlue() == OldColor.getBlue())
			return;
		streamln(r(c.getRed() / 255.0) + " " + r(c.getGreen() / 255.0) + " "
				+ r(c.getBlue() / 255.0) + " rg");
		OldColor = c;
	}

	@Override
	public void setColor(final ConstructionObject o) {
		if (o.isJobTarget())
			setColor(ZirkelFrame.TargetColor);
		else if (o.indicated())
			setColor(ZirkelFrame.IndicateColor);
		else if (o.selected())
			setColor(ZirkelFrame.SelectColor);
		else {
			if (o.getColorType() == ConstructionObject.THIN) {
				final int i = o.getColorIndex();
				if (o.isHidden())
					setColor(ZirkelFrame.BrighterLightColors[i]);
				else
					setColor(ZirkelFrame.LightColors[i]);
			} else {
				final int i = o.getColorIndex();
				if (o.isHidden())
					setColor(ZirkelFrame.BrighterColors[i]);
				else
					setColor(ZirkelFrame.Colors[i]);
			}
		}
		if (o.getColorType() == ConstructionObject.THIN) {
			setStroke(Thin);
		} else if (o.getColorType() == ConstructionObject.THICK) {
			setStroke(Thick);
		} else {
			setStroke(Normal);
		}
	}

	Color OldFillColor = null;

	public void setFillColor(final Color c) {
		if (OldFillColor != null && c.getRed() == OldFillColor.getRed()
				&& c.getGreen() == OldFillColor.getGreen()
				&& c.getBlue() == OldFillColor.getBlue())
			return;
		streamln(r(c.getRed() / 255.0) + " " + r(c.getGreen() / 255.0) + " "
				+ r(c.getBlue() / 255.0) + " rg");
		OldFillColor = c;
	}

	@Override
	public void setFillColor(final ConstructionObject o) {
		setStroke(Normal);
		if (o.isJobTarget())
			setColor(ZirkelFrame.TargetColor);
		else if ((o instanceof PointObject) && o.indicated())
			setFillColor(ZirkelFrame.IndicateColor);
		else {
			if (o.getColorType() != ConstructionObject.THICK) {
				final int i = o.getColorIndex();
				if (o.isHidden())
					setFillColor(ZirkelFrame.BrighterLightColors[i]);
				else
					setFillColor(ZirkelFrame.LightColors[i]);
			} else {
				final int i = o.getColorIndex();
				if (o.isHidden())
					setFillColor(ZirkelFrame.BrighterColors[i]);
				else
					setFillColor(ZirkelFrame.Colors[i]);
			}
		}
	}

	@Override
	public void setLabelColor(final ConstructionObject o) {
		if (o.labelSelected())
			setFillColor(ZirkelFrame.SelectColor);
		else if (o.isFilled()) {
			final int type = o.getColorType();
			o.setColorType(ConstructionObject.NORMAL);
			final int i = o.getColorIndex();
			setFillColor(ZirkelFrame.Colors[i]);
			o.setColorType(type);
		} else {
			final int type = o.getColorType();
			o.setColorType(ConstructionObject.NORMAL);
			final int i = o.getColorIndex();
			if (o.isHidden())
				setFillColor(ZirkelFrame.BrighterColors[i]);
			else
				setFillColor(ZirkelFrame.Colors[i]);
			o.setColorType(type);
		}
	}

	@Override
	public void drawRect(final double x, final double y, final double w,
			final double h) {
		streamln(r(x) + " " + ry(y + h) + " " + r(w) + " " + r(h) + " " + "re");
		streamln("S");
	}

	@Override
	public void drawLine(final double x, final double y, final double x1,
			final double y1, final ConstructionObject o) {
		streamln(r(x) + " " + ry(y) + " m");
		streamln(r(x1) + " " + ry(y1) + " l");
		streamln("S");
	}

	@Override
	public void drawLine(final double x, final double y, final double x1,
			final double y1) {
		setStroke(Normal);
		streamln(r(x) + " " + ry(y) + " m");
		streamln(r(x1) + " " + ry(y1) + " l");
		streamln("S");
	}

	@Override
	public void drawThickLine(final double x, final double y, final double x1,
			final double y1) {
		setStroke(Thick);
		streamln(r(x) + " " + ry(y) + " m");
		streamln(r(x1) + " " + ry(y1) + " l");
		streamln("S");
		setStroke(Normal);
	}

	public void drawArc(final double x, final double y, final double r,
			final double a, final double b) {
		final double f = r / Math.cos(b / 3);
		streamln(r(x + Math.cos(a + b / 3) * f) + " "
				+ ry(y - Math.sin(a + b / 3) * f) + " "
				+ r(x + Math.cos(a + 2 * b / 3) * f) + " "
				+ ry(y - Math.sin(a + 2 * b / 3) * f) + " "
				+ r(x + Math.cos(a + b) * r) + " "
				+ ry(y - Math.sin(a + b) * r) + " c");
	}

	@Override
	public void drawArc(double x, double y, final double w, final double h,
			double a, double b) {
		setStroke(Normal);
		final double r = w / 2;
		x += r;
		y += r;
		a = a / 180 * Math.PI;
		b = b / 180 * Math.PI;
		int n = (int) (r * b / 10);
		if (n < 4)
			n = 4;
		streamln(r(x + Math.cos(a) * r) + " " + ry(y - Math.sin(a) * r) + " m");
		for (int i = 0; i < n; i++) {
			drawArc(x, y, r, a + i * b / n, b / n);
		}
		streamln("S");
	}

	@Override
	public void drawArc(double x, double y, final double w, final double h,
			double a, double b, final ConstructionObject o) {
		final double r = w / 2;
		x += r;
		y += r;
		a = a / 180 * Math.PI;
		b = b / 180 * Math.PI;
		int n = (int) (r * b / 10);
		if (n < 4)
			n = 4;
		streamln(r(x + Math.cos(a) * r) + " " + ry(y + -Math.sin(a) * r) + " m");
		for (int i = 0; i < n; i++) {
			drawArc(x, y, r, a + i * b / n, b / n);
		}
		streamln("S");
	}

	@Override
	public void setFont(final int size, final boolean bold) {
		Bold = bold;
		FM = new PdfFontMetrics(size);
		streamln("/F1 " + size + " Tf");
	}

	@Override
	public FontMetrics getFontMetrics() {
		if (FM == null)
			FM = new PdfFontMetrics(20);
		return FM;
	}

	@Override
	public void drawString(final String s, final double x, final double y) {
		if (s.length() == 1) {
			final char c = s.charAt(0);
			for (int i = 0; i < Translation.length / 2; i++) {
				if (Translation[2 * i + 1] == c) {
					streamln("BT");
					streamln("/F2 " + FM.Size + " Tf");
					streamln(r(x) + " " + ry(y) + " Td");
					streamln("(" + Translation[2 * i] + ") Tj");
					streamln("ET");
					return;
				}
			}
		}
		streamln("BT");
		if (Bold)
			streamln("/F3 " + FM.Size + " Tf");
		else
			streamln("/F1 " + FM.Size + " Tf");
		streamln(r(x) + " " + ry(y) + " Td");
		streamln("(" + s + ") Tj");
		streamln("ET");
	}

	public static char Translation[] = { 'a', '\u03B1', 'A', '\u0391', 'b',
		'\u03B2', 'B', '\u0392', 'c', '\u03B3', 'C', '\u0393', 'd',
		'\u03B4', 'D', '\u0394', 'e', '\u03B5', 'E', '\u0395', 'f',
		'\u03D5', 'F', '\u03A6', 'g', '\u03B3', 'G', '\u0393', 'h',
		'\u03B7', 'H', '\u0397', 'i', '\u03B9', 'I', '\u0399', 'k',
		'\u03BA', 'K', '\u039A', 'l', '\u03BB', 'L', '\u039B', 'm',
		'\u03BC', 'M', '\u039C', 'n', '\u03BD', 'N', '\u039D', 'o',
		'\u03BF', 'O', '\u03A9', 'p', '\u03C0', 'P', '\u03A0', 'q',
		'\u03C7', 'Q', '\u03A7', 'r', '\u03C1', 'R', '\u03A1', 's',
		'\u03C3', 'S', '\u03A3', 't', '\u03C4', 'T', '\u03A4', 'u',
		'\u03C5', 'U', '\u03A5', 'v', '\u03C8', 'V', '\u03A8', 'w',
		'\u03C9', 'W', '\u03A9', 'x', '\u03BE', 'X', '\u039E', 'y',
		'\u03C7', 'Y', '\u03A7', 'z', '\u03B6', 'Z', '\u0396', };

	@Override
	public void drawOval(final double x, final double y, final double w,
			final double h) {
		drawArc(x, y, w, h, 0, 360);
	}

	@Override
	public void drawOval(final double x, final double y, final double w,
			final double h, final ConstructionObject o) {
		drawArc(x, y, w, h, 0, 360, o);
	}

	@Override
	public void fillRect(final double x, final double y, final double w,
			final double h, final boolean outline, final boolean transparent,
			final ConstructionObject o) {
		setFillColor(o);
		if (outline)
			setColor(o);
		streamln(r(x) + " " + ry(y + h) + " " + r(w) + " " + r(h) + " " + "re");
		streamln(outline ? "b" : "f");
	}

	@Override
	public void fillOval(final double x, final double y, final double w,
			final double h, final boolean outline, final boolean transparent,
			final ConstructionObject o) {
		fillArc(x, y, w, h, 0, 360, outline, transparent, true, o);
	}

	@Override
	public void fillPolygon(final double x[], final double y[], final int n,
			final boolean outline, final boolean tranparent,
			final ConstructionObject o) {
		setFillColor(o);
		if (outline)
			setColor(o);
		streamln(r(x[0]) + " " + ry(y[0]) + " m");
		for (int i = 1; i < n; i++) {
			streamln(r(x[i]) + " " + ry(y[i]) + " l");
		}
		streamln(outline ? "b*" : "f*");
	}

	@Override
	public void fillArc(double x, double y, final double w, final double h,
			double a, double b, final boolean outline,
			final boolean transparent, final boolean arc,
			final ConstructionObject o) {
		setFillColor(o);
		if (outline)
			setColor(o);
		final double r = w / 2;
		x += r;
		y += r;
		a = a * Math.PI / 180;
		b = b * Math.PI / 180;
		int n = (int) (r * b / 10);
		if (n < 4)
			n = 4;
		streamln(r(x + Math.cos(a) * r) + " " + ry(y - Math.sin(a) * r) + " m");
		for (int i = 0; i < n; i++) {
			drawArc(x, y, r, a + i * b / n, b / n);
		}
		if (arc) {
			streamln(r(x) + " " + ry(y) + " l");
		}
		streamln(outline ? "b" : "f");
	}

	@Override
	public void drawImage(final Image i, final int x, final int y,
			final ImageObserver o) {
	}

	@Override
	public void drawImage(final Image i, final int x, final int y, final int w,
			final int h, final ImageObserver o) {
	}

	public static void main(final String args[]) {
		try {
			new ConstructionObject(new Construction());
			final PrintWriter out = new PrintWriter(new FileOutputStream(
			"test.pdf"));
			final MyGraphicsPDF pdf = new MyGraphicsPDF(out, 1000, 1000, 1);
			pdf.streamln("1 0 0 -1 0 1000 cm");
			pdf.drawRect(300, 300, 400, 400);
			for (int i = 0; i < 180; i++) {
				final double s = i / 180.0 * Math.PI;
				pdf.drawLine(500 + Math.cos(s) * 200, 500 + Math.sin(s) * 200,
						500 + Math.cos(s) * 200, 500 + Math.sin(s) * 200);
			}
			pdf.setColor(Color.red);
			pdf.drawArc(-300, -300, 1000, 1000, 0, 360);
			pdf.close();
			out.close();
		} catch (final Exception e) {
			System.out.println(e);
		}
	}

	int fsize;
	boolean flarge, fbold;
	int ffactor = Global.getParameter("ffactor", 130);

	@Override
	public void setDefaultFont(final int h, final boolean large,
			final boolean bold) {
		fsize = h;
		flarge = large;
		fbold = bold;
		ffactor = Global.getParameter("ffactor", 130);
		setFont(large, bold);
	}

	@Override
	public void setFont(final boolean large, final boolean bold) {
		int size = fsize;
		if (large)
			size = size * ffactor / 100;
		if (flarge)
			size = size * ffactor / 100;
		setFont(size, bold || fbold);
	}

	@Override
	public void drawImage(final Image i, final double x, final double y,
			final double x1, final double y1, final double x2, final double y2,
			final ImageObserver o) {
	}

	@Override
	public Graphics getGraphics() {
		return null;
	}

	@Override
	public int stringWidth(final String s) {
		return getFontMetrics().stringWidth(s);
	}

	@Override
	public int stringHeight(final String s) {
		return getFontMetrics().getHeight();
	}

	@Override
	public int drawStringExtended(final String s, final double x, final double y) {
		drawString(s, x, y + getFontMetrics().getAscent());
		return getFontMetrics().getHeight();
	}

	@Override
	public int stringAscent(final String s) {
		return getFontMetrics().getAscent();
	}

	@Override
	public void fillOval(final double x, final double y, final double w,
			final double h, final Color WithColor) {
	}

	@Override
	public void fillRect(final double x, final double y, final double w,
			final double h, final Color WithColor) {
	}

	@Override
	public void drawDiamond(final double x, final double y, final double w,
			final boolean isThick, final ConstructionObject o) {
	}

	@Override
	public void drawDcross(final double x, final double y, final double w,
			final boolean isThick, final ConstructionObject o) {
	}

	@Override
	public void setAntialiasing(final boolean bool) {
	}

	@Override
	public void drawAxisLine(final double x, final double y, final double x1,
			final double y1) {
	}

	@Override
	public void fillPolygon(final double[] x, final double[] y, final int n,
			final ConstructionObject o) {

	}
}
