/* $Id: MemoPadView.java 477 2011-12-02 09:04:43Z ohura $ */
package smart_gs.drawing_tool.view;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import javax.swing.ImageIcon;

import smart_gs.GSConstants;
import smart_gs.drawing_tool.ExLine2D;
import smart_gs.drawing_tool.ImageLabel;
import smart_gs.drawing_tool.drawing_mode.MemoPadMode;
import smart_gs.drawing_tool.view.View.ViewColor;
import smart_gs.logical.Preference;
import smart_gs.logical.Region;
import smart_gs.logical.URICreator;
import smart_gs.logical.region.MemoPadRegion;
import smart_gs.swingui.WorkspaceWindow;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class MemoPadView extends View {
	private static enum Direction{NORTH, NORTH_EAST, EAST, SOUTH_EAST, SOUTH, SOUTH_WEST, WEST, NORTH_WEST, CENTER}; 
	private static enum Mode{NORMAL, RESIZE, DELETE_ARROW, MOVE_ARROW};
	private static int LEFT_MARGIN = 10;
	private static int TOP_MARGIN = 5;
	private static int DEFAULT_FONT_SIZE = 15;
	private static Font DEFAULT_FONT = new Font("Serif", Font.BOLD, DEFAULT_FONT_SIZE);
	private static int id = 0;
	private static double MIN_WIDTH = 30;
	private static double MIN_HEIGHT = 20;
	private static double DEFAULT_CORNER_SIZE = 17;

	private static int BARB_ANGLE = 20;
	private static final double DEFAULT_BARB_LENGTH = 12;
	public static String defaultMemoPadText = "Double click to input a text.";
	public static enum MemoPadColor{YELLOW, CYAN, MAGENTA, GREEN, TRANSPARENT, OPAQUE}
	public static Color MEMOPAD_YELLOW = new Color(255,255,153);
	public static Color MEMOPAD_CYAN = new Color(204,255,255);
	public static Color MEMOPAD_MAGENTA = new Color(255,153,255);
	public static Color MEMOPAD_GREEN = new Color(153,255,0);

	protected Rectangle2D rectangle;
	protected String memopadtext;
	protected Font font = DEFAULT_FONT;
	private double corner_size = DEFAULT_CORNER_SIZE;
	private double barb_length = DEFAULT_BARB_LENGTH;
	private ArrayList<Arrow> arrows;
	private Mode mode;
	private int index_of_moving_arrow = -1;
	private Color color = stringToColor(Preference.getInstance().getMemoPadColor());
	private Color stroke_color = Color.BLACK; 
	private boolean transparency = false;
	private ArrayList<Line2D> lines = new ArrayList<Line2D>();

	public MemoPadView(Rectangle2D rectangle,String memopadtext) {
		super();
		this.rectangle = rectangle;
		this.memopadtext = memopadtext;
//		this.defaultColor = Color.yellow;
		this.setArrows(new ArrayList<Arrow>());
		this.mode = Mode.NORMAL;
		MemoPadView.id++;
	}	
	public MemoPadView(Rectangle2D rectangle,String memopadtext,boolean isProtected){
		this(rectangle,memopadtext);
		this.isProtected = isProtected;
	}
	public MemoPadView(Rectangle2D rectangle,String memopadtext,boolean isSelected,boolean isEmphasized, boolean isProtected){
		this(rectangle,memopadtext);
		this.isSelected = isSelected;
		this.isEmphasized = isEmphasized;
		this.isProtected = isProtected;
	}

	public String getMemoPadText() {
		return memopadtext;
	}
	public void setMemoPadText(String memopadtext) {
		this.memopadtext = memopadtext;
	}
	public boolean containsInArrow(Point2D point) {
		ArrayList<Arrow> arrows = this.arrows;
		for(int i = 0; i < arrows.size(); i++) {
			if (arrows.get(i).contains(point)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean contains(Point2D point) {
//		return (this.rectangle.contains(point) || this.containsInArrow(point));
		return this.rectangle.contains(point);
	}
	@Override
	public void drawShape(Graphics2D g) {
		this.setBackgroundColor(g);
		g.fill(this.rectangle);
		this.setFrameStroke(g);
		g.draw(this.rectangle);
		float x = (float) this.rectangle.getX();
		float y = (float) this.rectangle.getY();
		double rect_width = this.rectangle.getWidth();
		double rect_height = this.rectangle.getHeight();
		g.setFont(this.font);
		FontMetrics fm = g.getFontMetrics();
		ArrayList<String> list = wrap(this.getMemoPadText(), rect_width - (LEFT_MARGIN * 2), fm);
		int fm_height = fm.getHeight();
		int asc = fm.getAscent();
		if (this.isSelected) {
			for (double d = corner_size; d > 0; d = d - 1) {
				g.draw(new Line2D.Double(x + rect_width - d, y + rect_height, x + rect_width, y + rect_height - d));
			}
		}
		if (!transparency) {
			g.setColor(Color.BLACK);
		}
		drawStringList(g, list, (int) x + LEFT_MARGIN,  (int) y + TOP_MARGIN + asc, fm_height, rect_height);
		
		for (int i = 0; i < getArrows().size();i++) {
			if (!this.contains(getArrows().get(i).head)) {
				g.setStroke(new BasicStroke(2));
				getArrows().get(i).drawArrow(g, isSelected, stroke_color);
			}
		}
		if (this.isProtected) {
			double px = x - 4;
			double py = y - 4;
			double pw = rect_width + 8;
			double ph = rect_height + 8;
			g.setColor(Color.BLACK);
			g.setStroke(new BasicStroke(1));
			g.draw(new Rectangle2D.Double(px, py, pw, ph));
		}
	}
	public void setBackgroundColor(Graphics2D g) {
		g.setColor(this.color);
	}
	public void setStrokeColor(Graphics2D g) {
		if (this.isProtected) {
			g.setColor(new Color(51, 102,0));
		} else {
			g.setColor(Color.BLACK);
		}
	}
	public void setFrameStroke(Graphics2D g) {
		if (this.isSelected) {
			g.setColor(Color.BLACK);
			g.setStroke(new BasicStroke(2));
		} else {
			g.setColor(stroke_color);
			g.setStroke(new BasicStroke(1));
		}
		if (this.isProtected) {
			
		}
	}
	@Override
	public int getType() {
		return View.MEMOPAD;
	}
	@Override
	public void moveBy(Point2D d) {
		double x = this.rectangle.getX();
		double y = this.rectangle.getY();
		double width = this.rectangle.getWidth();
		double height = this.rectangle.getHeight();
		this.rectangle = new Rectangle2D.Double(x+d.getX(),y+d.getY(),width,height);
		for (int i = 0; i < arrows.size();i++) {
			arrows.get(i).tail = getArrowPort(arrows.get(i).head);
		}
	}
	@Override
	public MemoPadView enlargedView(double ratio, double gapX, double gapY) {
		double x = this.rectangle.getX();
		double y = this.rectangle.getY();
		double w = this.rectangle.getWidth();
		double h = this.rectangle.getHeight();
		String text = this.memopadtext;
		double cs = corner_size * ratio;
		double bl = barb_length * ratio;
		Rectangle2D rect = new Rectangle2D.Double(x*ratio+gapX,y*ratio+gapY,w*ratio,h*ratio);
		MemoPadView view = new MemoPadView(rect, text, this.isSelected,this.isEmphasized, this.isProtected);
		view.corner_size = cs;
		view.barb_length = bl;
		view.mode = this.mode;
		view.font = this.font;
		view.color = this.color;
		view.stroke_color = this.stroke_color;
		view.transparency = this.transparency;
		ArrayList<Arrow> list = new ArrayList<Arrow>();
		for (int i = 0; i < getArrows().size(); i++) {
			Arrow arrow = getArrows().get(i);
			list.add(view.arrowTo(new Point2D.Double(arrow.head.getX() * ratio + gapX, arrow.head.getY() * ratio + gapY)));
		}
		view.setArrows(list);
		return view;
	}
	public Rectangle2D getRectangle2D() {
		return this.rectangle;
	}
	@Override
	public String getTypeString() {
		return URICreator.MEMOPAD;
	}
	@Override
	public void drawLinkedShape(Graphics2D g, Image image) {
		this.drawShape(g);
		this.drawLinkIcon(g, image);
	}
	@Override
	public Element createXMLElement(Document document) {
		Element element = document.createElement("view");
		element.setAttribute("type",URICreator.MEMOPAD);
		element.setAttribute("x",this.rectangle.getX()+"");
		element.setAttribute("y",this.rectangle.getY()+"");	
		element.setAttribute("width",this.rectangle.getWidth()+"");
		element.setAttribute("height",this.rectangle.getHeight()+"");
		element.setAttribute("text", this.memopadtext+"");
		element.setAttribute("arrows", arrowsToString(arrows));
		element.setAttribute("color", colorToString(this.color));
		element.setAttribute("transparency", this.transparency+"");
		if (this.isProtected()) {
			element.setAttribute("protection", "PROTECTED");			
		}
		return element;
	}
	public static View restore(Element elem) {
		double x = new Double(elem.getAttribute("x"));
		double y = new Double(elem.getAttribute("y"));
		double width = new Double(elem.getAttribute("width"));
		double height = new Double(elem.getAttribute("height"));
		String text = new String(elem.getAttribute("text"));
		boolean isProtected = elem.getAttribute("protection").equals("PROTECTED");
		MemoPadView view = new MemoPadView(new Rectangle2D.Double(x,y,width,height), text,isProtected);
		view.arrows = MemoPadView.stringToArrows(elem.getAttribute("arrows"));
		view.color = MemoPadView.stringToColor(elem.getAttribute("color"));
		if (new Boolean(elem.getAttribute("transparency"))) {
			view.transparentise();
		} else {
			view.opaque();
		}
		return view;
	}
	public void setRectangle(Rectangle2D rectangle) {
		this.rectangle = rectangle;
	}
	public Font getFont() {
		return this.font;
	}
	public static ArrayList<String> wrap(String text, double width, FontMetrics fm) {
		ArrayList<String> list = new ArrayList<String>(); 
		int i = 0;
		boolean cr = false;
		while (i < text.length()){
			String s = "";
			int w = 0;
			while (w < width && i < text.length() && !cr) {
				if (i+2 < text.length() && (text.substring(i, i+1).equals("\n"))) {
					i += 1;
					cr = true;
				} else {
					s = s + text.charAt(i);
					w = fm.stringWidth(s);
					i++;
				}
			}
			if (s == "") {
				return list;
			}
			if (s.endsWith("\\\\")) {
				s = s.substring(0, s.length() - 2);
			}
			list.add(s);
			cr = false;
		}
		return list;
	}
	public static void drawStringList(Graphics2D g,  ArrayList<String> text_list, int x, int y, int text_height, double rect_height) {
		int current_height = text_height; 
		for (int i = 0; i < text_list.size(); i++) {
			if (current_height >= rect_height) {
				return;
			} else {
				if (i != text_list.size() - 1 && current_height + text_height >= rect_height) {
					String s = text_list.get(i);
					int l = s.length();
					if (l >= 1) {
						String ss = s.substring(0, l-1) + "..";
						g.drawString(ss, x, y);
					}
					return;			
				} else {
					g.drawString(text_list.get(i), x, y);
				}
				y += text_height;
				current_height += text_height;
			}
		}
	}
	@Override
	public void resize(Point2D d) {
		double x = this.rectangle.getX();
		double y = this.rectangle.getY();
		double width, height;
		if (d.getX() > x + MIN_WIDTH) {
			width = d.getX() - x;
		} else {
			width = MIN_WIDTH;
		}
		if (d.getY() > y + MIN_HEIGHT) {
			height = d.getY() - y;
		} else {
			height = MIN_HEIGHT;
		}
		this.rectangle = new Rectangle2D.Double(x, y, width, height);
	}
	public void arrowMoveBy(int index, Point2D d) {
		if (index >= 0) {
			Point2D point = new Point2D.Double(d.getX(), d.getY());
			this.arrows.set(index, arrowTo(point));
		}
	}
	@Override
	public boolean containsInCorner(Point2D point) {
		if (this.contains(point)) {
			Rectangle2D rect = this.rectangle;
			double x = rect.getX();
			double y = rect.getY();
			double w = rect.getWidth();
			double h = rect.getHeight();
			if (x + w - corner_size <= point.getX() && y + h - corner_size <= point.getY()) {
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}
	}
	@Override
	public boolean isResizable() {
		return this.mode == Mode.RESIZE;
	}
	public void setResizeMode(boolean b) {
		if (b) {
			this.mode = Mode.RESIZE;
		} else {
			this.mode = Mode.NORMAL;
		}
	}
	public Point2D getArrowPort(Point2D point) {
		Direction dir = getDirection(point);
		double x = this.rectangle.getX();
		double y = this.rectangle.getY();
		double width = this.rectangle.getWidth();
		double height = this.rectangle.getHeight();
		switch(dir) {
		case NORTH:
			return new Point2D.Double(x + (width/2), y);
		case NORTH_EAST:
			return new Point2D.Double(x + width, y);
		case EAST:
			return new Point2D.Double(x + width, y + (height/2));
		case SOUTH_EAST:
			return new Point2D.Double(x + width, y + height);
		case SOUTH:
			return new Point2D.Double(x + (width/2), y + height);
		case SOUTH_WEST:
			return new Point2D.Double(x, y + height);
		case WEST:
			return new Point2D.Double(x, y + (height/2));
		case NORTH_WEST:
			return new Point2D.Double(x, y);
		default:
			return new Point2D.Double(x + (width/2), y + (height /2));
		}
	}
	public Direction getDirection(Point2D point) {
		double x = point.getX();
		double y = point.getY();
		Rectangle2D rect = this.rectangle;
		double w = rect.getX() + 1;
		double e = rect.getX() + rect.getWidth() - 1;
		double n = rect.getY() + 1;
		double s = rect.getY() + rect.getHeight() - 1;
		if (w <= x && x < e && y < n) {
			return Direction.NORTH;
		} else if (e <= x && y < n) {
			return Direction.NORTH_EAST;
		} else if (e <= x && n <= y && y <= s) {
			return Direction.EAST;
		} else if (e <= x && s < y) {
			return Direction.SOUTH_EAST;
		} else if (w <= x && x < e && s < y) {
			return Direction.SOUTH;
		} else if (x < w && s < y) {
			return Direction.SOUTH_WEST;
		} else if (x < w && n < y && y <= s) {
			return Direction.WEST;
		} else if (x < w && y <= n){
			return Direction.NORTH_WEST;
		} else {
			return Direction.CENTER;
		}
	}
	public void addArrow(Arrow arrow) {
		this.getArrows().add(arrow);
	}
	public Arrow arrowTo(Point2D head) {
		Point2D tail = getArrowPort(head);
		return new Arrow(tail, head, this.barb_length);
	}
	public void deleteArrow(Point2D point) {
		for (int i = getArrows().size() - 1; i >= 0; i--) {
			if (getArrows().get(i).containsInHead(point)) {
				getArrows().remove(i);
				WorkspaceWindow.setUpdated(true);
				return;
			}
		}
	}
	private static boolean neighbour(Point2D p1, Point2D p2, double r) {
		return getDistance(p1, p2) <= r;
	}
	private static double getDistance(Point2D p1, Point2D p2) {
		double x1 = p1.getX();
		double y1 = p1.getY();
		double x2 = p2.getX();
		double y2 = p2.getY();
		return Math.sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)));
	}
	public void setArrows(ArrayList<Arrow> arrows) {
		this.arrows = arrows;
	}
	public ArrayList<Arrow> getArrows() {
		return arrows;
	}
	public void setDeleteArrowPointMode(boolean b) {
		if (b) {
			this.mode = Mode.DELETE_ARROW;
		} else {
			this.mode = Mode.NORMAL;
		}
	}
	public int IndexOfArrowContaining(Point2D point) {
		for (int i = arrows.size() -1; i >= 0; i--) {
			if (arrows.get(i).containsInHead(point)) {
				return i;
			}
		}
		return -1;
	}
	public void setIndexOfMovingArrow(int index) {
		this.index_of_moving_arrow = index;
	}
	public void setIndexOfMovingArrow(Point2D point) {
		for (int i = arrows.size() - 1; i >= 0; i--) {
			if (arrows.get(i).containsInHead(point)) {
				this.index_of_moving_arrow = i;
				return;
			}
		}
		this.index_of_moving_arrow = -1;
	}	
	public int getIndexOfMovingArrow() {
		return this.index_of_moving_arrow;
	}
	public boolean containsInArrowHead(Point2D point) {
		for (int i = 0; i < arrows.size(); i++) {
			if (arrows.get(i).containsInHead(point)) {
				return true;
			}
		}
		return false;
	}
	public void setColor(Color c) {
		this.color = c;
		if (transparency) {
			this.transparentise();
		}
		WorkspaceWindow.getInstance().repaint();
	}
	@Override
	public void setViewColor(ViewColor c) {
		switch(c) {
		case YELLOW:
			this.color = MemoPadView.MEMOPAD_YELLOW;
			if (transparency) {
				this.transparentise();
			} else {
				this.opaque();
			}
			WorkspaceWindow.getInstance().repaint();
			return;
		case CYAN:
			this.color = MemoPadView.MEMOPAD_CYAN;
			if (transparency) {
				this.transparentise();
			} else {
				this.opaque();
			}
			WorkspaceWindow.getInstance().repaint();
			return;
		case MAGENTA:
			this.color = MemoPadView.MEMOPAD_MAGENTA;
			if (transparency) {
				this.transparentise();
			} else {
				this.opaque();
			}
			WorkspaceWindow.getInstance().repaint();
			return;
		case GREEN:
			this.color = MemoPadView.MEMOPAD_GREEN;
			if (transparency) {
				this.transparentise();
			} else {
				this.opaque();
			}
			WorkspaceWindow.getInstance().repaint();
			return;
		case TRANSPARENT:
			this.transparentise();
			WorkspaceWindow.getInstance().repaint();
			return;
		case OPAQUE:
			this.opaque();
			WorkspaceWindow.getInstance().repaint();
			return;
		default:
			this.color = MemoPadView.MEMOPAD_GREEN;
			return;
		}
	}
	public void transparentise() {
		this.transparency = true;
		int red = color.getRed();
		int green = color.getGreen();
		int blue = color.getBlue();
		this.color = new Color(red, green, blue, 120);
		this.stroke_color = new Color(red, green, blue, 255);
	}
	public void opaque() {
		this.transparency = false;
		int red = color.getRed();
		int green = color.getGreen();
		int blue = color.getBlue();
		this.color = new Color(red, green, blue, 255);
		this.stroke_color = Color.BLACK;
	}
	public boolean isTransparent() {
		return this.transparency;
	}
	public static String colorToString(Color c) {
		if (c.equals(MemoPadView.MEMOPAD_YELLOW)) {
			return "yellow";
		} else if (c.equals(MemoPadView.MEMOPAD_CYAN)) {
			return "cyan";
		} else if (c.equals(MemoPadView.MEMOPAD_MAGENTA)){
			return "magenta";
		} else {
			return "green";
		}
	}
	public static Color stringToColor(String s) {
		if (s.equals("yellow")) {
			return MemoPadView.MEMOPAD_YELLOW;
		} else if (s.equals("cyan")) {
			return MemoPadView.MEMOPAD_CYAN;
		} else if (s.equals("magenta")) {
			return MemoPadView.MEMOPAD_MAGENTA;
		} else {
			return MemoPadView.MEMOPAD_GREEN;
		}
	}
	private static String arrowsToString(ArrayList<Arrow> list) {
		String s = "";
		for (int i = 0; i < list.size(); i++) {
			s += list.get(i).toString() + "/";
		}
		if (s.length() > 0) {
			s = s.substring(0, s.length()-1);
		}
		return s;
	}
	private static ArrayList<Arrow> stringToArrows(String s) {
		ArrayList<Arrow> list = new ArrayList<Arrow>();
		if (s.equals("")) {
			return list;
		}
		String[] slist = s.split("/");
		for (int i = 0; i < slist.length; i++) {
			list.add(Arrow.stringToArrow(slist[i]));
		}
		return list;
	}
	
	private static class Arrow {
		private Point2D tail;
		private Point2D head;
		private double barbLength;
		
		public Arrow(Point2D tail, Point2D head, double barbLength) {
			this.tail = tail;
			this.head = head;
			this.barbLength = barbLength;
		}
		public Line2D getBarb(String side) {
			double slope = getSlope(tail, head);
			double theta = Math.toRadians(BARB_ANGLE);
			double slant = (side.equals("left") ? slope + theta : slope - theta);
			double x = head.getX() - (Math.cos(slant) * barbLength);
			double y = head.getY() - (Math.sin(slant) * barbLength);
			return new Line2D.Double(head, new Point2D.Double(x,y));
		}
		public double getSlope(Point2D ps, Point2D pe){
			double dy = pe.getY() - ps.getY();
			double dx = pe.getX() - ps.getX();
			return (Math.atan2(dy, dx));
		}
		public boolean containsInHead(Point2D point) {
			return neighbour(head, point, this.barbLength / 1.5);
		}

		public boolean contains(Point2D point) {
			ExLine2D line = new ExLine2D(this.tail, this.head);
			return View.lineContains(line, 5, point);
		}
		
		public void drawArrow(Graphics2D g, boolean selected, Color strokeColor) {
			g.draw(new Line2D.Double(tail, head));
			g.draw(this.getBarb("left"));
			g.draw(this.getBarb("right"));
			if (selected) {
				double r = this.barbLength / 1.5;
				double x = head.getX() - r;
				double y = head.getY() - r;
				g.setColor(Color.WHITE);
				g.fillOval((int)x, (int)y, (int)(r * 2), (int)(r * 2));
				g.setStroke(new BasicStroke(1));
				if (selected) {
					g.setColor(Color.BLACK);
				} else {
					g.setColor(strokeColor);
				}
				g.drawOval((int)x, (int)y, (int)(r * 2), (int)(r * 2));
			}
		}
		public String toString() {
			String hx = this.head.getX()+"";
			String hy = this.head.getY()+"";
			String tx = this.tail.getX()+"";
			String ty = this.tail.getY()+"";
			String bl = this.barbLength+"";
			return tx + "," + ty + "," + hx + "," + hy + "," + bl;
		}
		private static Arrow stringToArrow(String s) {
			String[] list = s.split(",");
			Point2D t = new Point2D.Double(new Double(list[0]), new Double(list[1]));
			Point2D h = new Point2D.Double(new Double(list[2]), new Double(list[3]));
			double bl = new Double(list[4]);
			return new Arrow(t, h, bl);
		}
		
	}

	@Override
	public Point getCenterPoint() {
		return new Point((int)this.getRectangle2D().getCenterX(),
							(int)this.getRectangle2D().getCenterY());
	}
	@Override
	public String getTypeStringForDisplay() {
		return "MemoPad";
	}
	@Override
	public void drawLinkIcon(Graphics2D g, Image image) {
		Rectangle rect = this.getRectangle();
		int w = image.getWidth(null) / 3;
		int h = image.getHeight(null) / 3;
		int x = (int)(rect.getX() + rect.getWidth() - w/3);
		int y = (int)(rect.getY()) - h/2;
		g.drawImage(image, x, y, w, h, null);
	}
}
