/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.draw.shapes;

import java.awt.Color;
import java.awt.Font;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.w3c.dom.Element;

import com.cburch.draw.model.AbstractCanvasObject;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeOption;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.util.UnmodifiableList;

public class SvgReader {
	private SvgReader() { }
	
	private static final Pattern PATH_REGEX = Pattern.compile("[a-zA-Z]|[-0-9.]+");
	
	public static AbstractCanvasObject createShape(Element elt) {
		String name = elt.getTagName();
		AbstractCanvasObject ret;
		if (name.equals("ellipse")) {
			ret = createOval(elt);
		} else if (name.equals("line")) {
			ret = createLine(elt);
		} else if (name.equals("path")) {
			ret = createPath(elt);
		} else if (name.equals("polyline")) {
			ret = createPolyline(elt);
		} else if (name.equals("polygon")) {
			ret = createPolygon(elt);
		} else if (name.equals("rect")) {
			ret = createRectangle(elt);
		} else if (name.equals("text")) {
			ret = createText(elt);
		} else {
			return null;
		}
		List<Attribute<?>> attrs = ret.getAttributes();
		if (attrs.contains(DrawAttr.PAINT_TYPE)) {
			String stroke = elt.getAttribute("stroke");
			String fill = elt.getAttribute("fill");
			if (stroke.equals("") || stroke.equals("none")) {
				ret.setValue(DrawAttr.PAINT_TYPE, DrawAttr.PAINT_FILL);
			} else if (fill.equals("none")) {
				ret.setValue(DrawAttr.PAINT_TYPE, DrawAttr.PAINT_STROKE);
			} else {
				ret.setValue(DrawAttr.PAINT_TYPE, DrawAttr.PAINT_STROKE_FILL);
			}
		}
		attrs = ret.getAttributes(); // since changing paintType could change it
		if (attrs.contains(DrawAttr.STROKE_WIDTH) && elt.hasAttribute("stroke-width")) {
			Integer width = Integer.valueOf(elt.getAttribute("stroke-width"));
			ret.setValue(DrawAttr.STROKE_WIDTH, width);
		}
		if (attrs.contains(DrawAttr.STROKE_COLOR)) {
			String color = elt.getAttribute("stroke");
			String opacity = elt.getAttribute("stroke-opacity");
			if (!color.equals("none")) {
				ret.setValue(DrawAttr.STROKE_COLOR, getColor(color, opacity));
			}
		}
		if (attrs.contains(DrawAttr.FILL_COLOR)) {
			String color = elt.getAttribute("fill");
			if (color.equals("")) color = "#000000";
			String opacity = elt.getAttribute("fill-opacity");
			if (!color.equals("none")) {
				ret.setValue(DrawAttr.FILL_COLOR, getColor(color, opacity));
			}
		}
		return ret;
	}
	
	private static AbstractCanvasObject createRectangle(Element elt) {
		int x = Integer.parseInt(elt.getAttribute("x"));
		int y = Integer.parseInt(elt.getAttribute("y"));
		int w = Integer.parseInt(elt.getAttribute("width"));
		int h = Integer.parseInt(elt.getAttribute("height"));
		if (elt.hasAttribute("rx")) {
			AbstractCanvasObject ret = new RoundRectangle(x, y, w, h);
			int rx = Integer.parseInt(elt.getAttribute("rx"));
			ret.setValue(DrawAttr.CORNER_RADIUS, Integer.valueOf(rx));
			return ret;
		} else {
			return new Rectangle(x, y, w, h);
		}
	}

	private static AbstractCanvasObject createOval(Element elt) {
		double cx = Double.parseDouble(elt.getAttribute("cx"));
		double cy = Double.parseDouble(elt.getAttribute("cy"));
		double rx = Double.parseDouble(elt.getAttribute("rx"));
		double ry = Double.parseDouble(elt.getAttribute("ry"));
		int x = (int) Math.round(cx - rx);
		int y = (int) Math.round(cy - ry);
		int w = (int) Math.round(rx * 2);
		int h = (int) Math.round(ry * 2);
		return new Oval(x, y, w, h);
	}
	
	private static AbstractCanvasObject createLine(Element elt) {
		int x0 = Integer.parseInt(elt.getAttribute("x1"));
		int y0 = Integer.parseInt(elt.getAttribute("y1"));
		int x1 = Integer.parseInt(elt.getAttribute("x2"));
		int y1 = Integer.parseInt(elt.getAttribute("y2"));
		return new Line(x0, y0, x1, y1);
	}
	
	private static AbstractCanvasObject createPolygon(Element elt) {
		return new Poly(true, parsePoints(elt.getAttribute("points")));
	}
	
	private static AbstractCanvasObject createPolyline(Element elt) {
		return new Poly(false, parsePoints(elt.getAttribute("points")));
	}
	
	private static AbstractCanvasObject createText(Element elt) {
		int x = Integer.parseInt(elt.getAttribute("x"));
		int y = Integer.parseInt(elt.getAttribute("y"));
		String text = elt.getTextContent();
		Text ret = new Text(x, y, text);
		
		String fontFamily = elt.getAttribute("font-family");
		String fontStyle = elt.getAttribute("font-style");
		String fontWeight = elt.getAttribute("font-weight");
		String fontSize = elt.getAttribute("font-size");
		int styleFlags = 0;
		if (fontStyle.equals("italic")) styleFlags |= Font.ITALIC;
		if (fontWeight.equals("bold")) styleFlags |= Font.BOLD;
		int size = Integer.parseInt(fontSize);
		ret.setValue(DrawAttr.FONT, new Font(fontFamily, styleFlags, size));
		
		String alignStr = elt.getAttribute("text-anchor");
		AttributeOption halign;
		if (alignStr.equals("start")) {
			halign = DrawAttr.ALIGN_LEFT;
		} else if (alignStr.equals("end")) {
			halign = DrawAttr.ALIGN_RIGHT;
		} else {
			halign = DrawAttr.ALIGN_CENTER;
		}
		ret.setValue(DrawAttr.ALIGNMENT, halign);
		
		// fill color is handled after we return
		return ret;
	}
	
	private static List<Location> parsePoints(String points) {
		Pattern patt = Pattern.compile("[ ,\n\r\t]+");
		String[] toks = patt.split(points);
		Location[] ret = new Location[toks.length / 2];
		for (int i = 0; i < ret.length; i++) {
			int x = Integer.parseInt(toks[2 * i]);
			int y = Integer.parseInt(toks[2 * i + 1]);
			ret[i] = Location.create(x, y);
		}
		return UnmodifiableList.create(ret);
	}
	
	private static AbstractCanvasObject createPath(Element elt) {
		Matcher patt = PATH_REGEX.matcher(elt.getAttribute("d"));
		List<String> tokens = new ArrayList<String>();
		int type = -1; // -1 error, 0 start, 1 curve, 2 polyline
		while (patt.find()) {
			String token = patt.group();
			tokens.add(token);
			if (Character.isLetter(token.charAt(0))) {
				switch (token.charAt(0)) {
				case 'M':
					if (type == -1) type = 0;
					else type = -1;
					break;
				case 'Q': case 'q':
					if (type == 0) type = 1;
					else type = -1;
					break;
				/* not supported
				case 'L': case 'l':
				case 'H': case 'h':
				case 'V': case 'v':
					if (type == 0 || type == 2) type = 2;
					else type = -1;
					break;
				*/
				default:
					type = -1;
				}
				if (type == -1) {
					throw new NumberFormatException("Unrecognized path command '" + token.charAt(0) + "'");
				}
			}
		}
	
		if (type == 1) {
			if (tokens.size() == 8 && tokens.get(0).equals("M")
					&& tokens.get(3).toUpperCase().equals("Q")) {
				int x0 = Integer.parseInt(tokens.get(1));
				int y0 = Integer.parseInt(tokens.get(2));
				int x1 = Integer.parseInt(tokens.get(4));
				int y1 = Integer.parseInt(tokens.get(5));
				int x2 = Integer.parseInt(tokens.get(6));
				int y2 = Integer.parseInt(tokens.get(7));
				if (tokens.get(3).equals("q")) {
					x1 += x0;
					y1 += y0;
					x2 += x0;
					y2 += y0;
				}
				Location e0 = Location.create(x0, y0);
				Location e1 = Location.create(x2, y2);
				Location ct = Location.create(x1, y1);
				return new Curve(e0, e1, ct);
			} else {
				throw new NumberFormatException("Unexpected format for curve");
			}
		} else {
			throw new NumberFormatException("Unrecognized path");
		}
	}
	
	private static Color getColor(String hue, String opacity) {
		int r;
		int g;
		int b;
		if (hue == null || hue.equals("")) {
			r = 0;
			g = 0;
			b = 0;
		} else {
			r = Integer.parseInt(hue.substring(1, 3), 16);
			g = Integer.parseInt(hue.substring(3, 5), 16);
			b = Integer.parseInt(hue.substring(5, 7), 16);
		}
		int a;
		if (opacity == null || opacity.equals("")) {
			a = 255;
		} else {
			a = (int) Math.round(Double.parseDouble(opacity) * 255);
		}
		return new Color(r, g, b, a);
	}
}
