package smart_gs.drawing_tool.drawing_mode;

import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.List;


import smart_gs.drawing_tool.ExLine2D;
import smart_gs.drawing_tool.LineSegEditorCanvas;
import smart_gs.drawing_tool.view.LineSegView;
import smart_gs.drawing_tool.view.LineView;
import smart_gs.logical.LineSegmentForEdit;
import smart_gs.smleditor.swingui.LineSegEditor;
import smart_gs.swingui.GSMouseEvent;
import smart_gs.swingui.WorkspaceWindow;
import smart_gs.util.GSLog;
import sml_editor.logical.LineDirection;

public class LineSegEditorEditVertexMode implements LineSegEditorMode {	
	
	private List<LineSegmentForEdit> linesForEdit;
	private LineSegEditor editor;
	private LineSegmentForEdit editingLineSegForEdit = null;
	private int editingLineSegForEditIndex = -1;
	private int editingVertexOrLineViewIndex = -1;
	private List<Point2D> editingVertexPoints = null;
	GSLog log = GSLog.getInstance();
	private boolean line_editing_mode = false;
	private Point2D startingPoint = null;
	private double start_in_original_position_x;
	private double start_in_original_position_y;
	private double end_in_original_position_x;
	private double end_in_original_position_y;

	public LineSegEditorEditVertexMode(LineSegEditor editor) {
		this.editor = editor;
		this.linesForEdit = this.editor.getLinesForEdit();

		this.editor.repaint();
	}

	@Override
	public void mouseClicked(GSMouseEvent e, LineSegEditorCanvas canvas) {
		if (e.getButton() != MouseEvent.BUTTON1 || ! WorkspaceWindow.getInstance().isAlt()) return;
		for (int i=this.linesForEdit.size()-1; i>=0; i--) {
			LineSegView tmp = linesForEdit.get(i).getView();
			int indexOfVertexToDelete = tmp.indexOfPressedVertex(e.getPoint2D());

			// This LineView is selected for vertex editing.
			if (indexOfVertexToDelete > -1) {
				this.editingLineSegForEditIndex = i;
				this.editingLineSegForEdit = this.linesForEdit.get(this.editingLineSegForEditIndex);
				this.editingVertexPoints = this.editingLineSegForEdit.getPoints();
				if (this.editingVertexPoints.size() < 4) return;
				this.editor.pushUndoStack();
				LineDirection direction = this.editingLineSegForEdit.getLineDirection();
				this.editingVertexPoints.remove(indexOfVertexToDelete);
				this.linesForEdit.remove(this.editingLineSegForEditIndex);
				this.linesForEdit.add(this.editingLineSegForEditIndex,(new LineSegmentForEdit(this.editor,this.editingVertexPoints,direction)).deepCopy());
				this.editor.setLinesForEdit(this.linesForEdit);
				this.editor.repaint();
				this.reset();
				return;
			}
		}
		
		this.editingLineSegForEditIndex = -1;
		Point2D clicked_point = e.getPoint2D();
		for (int i=this.linesForEdit.size()-1; i>=0; i--) {
			LineSegView tmp1 = linesForEdit.get(i).getView();
			int indexOfLineToAddNewVertex = tmp1.indexOfPressedLineView(clicked_point);

			if (indexOfLineToAddNewVertex > -1) {
				this.editingLineSegForEditIndex = i;
				this.editingLineSegForEdit = this.linesForEdit.get(this.editingLineSegForEditIndex);
				this.editingVertexPoints = this.editingLineSegForEdit.getPoints();
				// ln is the segment to add the new vertex.
				ExLine2D ln = this.editingLineSegForEdit.getView().getLine2D(indexOfLineToAddNewVertex);

				LineDirection direction = this.editingLineSegForEdit.getLineDirection();
				Point2D new_vertex = new Point2D.Double();
				if (ln.getStartPoint().getX() == ln.getEndPoint().getX()) {
					new_vertex.setLocation(ln.getStartPoint().getX(),clicked_point.getY());
				} else if (ln.getStartPoint().getY() == ln.getEndPoint().getY()) {
					new_vertex.setLocation(clicked_point.getX(),ln.getStartPoint().getY());
				} else {
					double m1, m2, b1, b2;
			        m1 = (ln.getEndPoint().getY() - ln.getStartPoint().getY()) / (ln.getEndPoint().getX() - ln.getStartPoint().getX());
			        b1 = ln.getStartPoint().getY() - (m1 * ln.getStartPoint().getX());
			        m2 = -1.0 / m1;
			        b2 = clicked_point.getY() - (m2 * clicked_point.getX());

			        new_vertex.setLocation((b2 - b1) / (m1 - m2),(b2 * m1 - b1 * m2) / (m1 - m2));
				}
				Point2D start = ln.getStartPoint();
				Point2D end = ln.getEndPoint();
				double x = new_vertex.getX();
				double y = new_vertex.getY();
				double sx = start.getX();
				double sy = start.getY();
				double ex = end.getX();
				double ey = end.getY();
				if( (sx <= x && x <= ex  &&  sy <= y && y <= ey) || 
						(sx <= x && x <= ex  &&  sy >= y && y >= ey) || 
						(sx >= x && x >= ex  &&  sy <= y && y <= ey) || 
						(sx >= x && x >= ex  &&  sy >= y && y >= ey) ) {
					if ((new_vertex.distance(start) < 7.0d || new_vertex.distance(end) < 7.0d)) {
						GSLog.getInstance().warnWithWindow("Too near to start or end points.");
						this.reset();
						return;
					}
				} else {
					GSLog.getInstance().warn("Cannot compute.");
					this.reset();
					return;
				}

				this.editor.pushUndoStack();
				this.linesForEdit.remove(this.editingLineSegForEditIndex);
				this.editingVertexPoints.add(indexOfLineToAddNewVertex+1,new_vertex);
				this.linesForEdit.add(this.editingLineSegForEditIndex,new LineSegmentForEdit(this.editor,this.editingVertexPoints,direction));
				this.editor.setLinesForEdit(this.linesForEdit);
				this.editor.repaint();
			}
		}
		this.reset();
	}

	@Override
	public void mouseEntered(GSMouseEvent e, LineSegEditorCanvas canvas) {
		// NOP

	}

	@Override
	public void mouseExited(GSMouseEvent e, LineSegEditorCanvas canvas) {
		// NOP

	}

	@Override
	public void mousePressed(GSMouseEvent e, LineSegEditorCanvas canvas) {
		if (e.getButton() == MouseEvent.BUTTON1){
			if (WorkspaceWindow.getInstance().isAlt()) return;
			
			this.line_editing_mode = false;
			// editing vertexes
			if (this.editingLineSegForEdit == null && this.editingVertexOrLineViewIndex == -1) {
				for (int i=this.linesForEdit.size()-1; i>=0; i--) {
					LineSegView tmp = linesForEdit.get(i).getView();
					int ans = tmp.indexOfPressedVertex(e.getPoint2D());

					// This LineView is selected for vertex editing.
					if (ans > -2) {
						this.editingLineSegForEditIndex = i;
						this.editingLineSegForEdit = this.linesForEdit.get(this.editingLineSegForEditIndex);
						this.editingVertexPoints = this.editingLineSegForEdit.getPoints();

						if (ans > -1) this.editingVertexOrLineViewIndex = ans;
					}
				}
				return;
			} else if (this.editingLineSegForEdit != null && this.editingVertexOrLineViewIndex == -1) {
				LineSegView tmp = this.editingLineSegForEdit.getView();
				int indexOfPressedVertex = tmp.indexOfPressedVertex(e.getPoint2D());
				if (indexOfPressedVertex > -1) {
					this.editingVertexOrLineViewIndex = indexOfPressedVertex;
				}
				else {
					// No pressed vertex, thus find the first pressed line.
					this.editingLineSegForEditIndex = -1;
					for (int i=this.linesForEdit.size()-1; i>=0; i--) {
						LineSegView tmp1 = linesForEdit.get(i).getView();
						int indexOfPressedLine = tmp1.indexOfPressedLineView(e.getPoint2D());

						// This LineView is selected for line editing.
						if (indexOfPressedLine > -1) {
							// editing lines
							this.startingPoint = e.getPoint2D();
							this.line_editing_mode = true;
							this.editingVertexOrLineViewIndex = indexOfPressedLine;
							this.editingLineSegForEditIndex = i;
							this.editingLineSegForEdit = this.linesForEdit.get(this.editingLineSegForEditIndex);
							this.editingVertexPoints = this.editingLineSegForEdit.getPoints();

							int size = this.editingVertexPoints.size();
							Point2D start = this.editingVertexPoints.get(this.editingVertexOrLineViewIndex);
							Point2D end;
							if (this.editingVertexOrLineViewIndex == size-1) end = this.editingVertexPoints.get(0);
							else end = this.editingVertexPoints.get(this.editingVertexOrLineViewIndex+1);

							this.start_in_original_position_x = start.getX();
							this.start_in_original_position_y = start.getY();
							this.end_in_original_position_x = end.getX();
							this.end_in_original_position_y = end.getY();
							break;
						}
					}
				} 
				log.debug("In LineSegEditorEditVertexMode, this.editingLineSegForEdit == null && this.editingVertexIndex != -1");
				return;
			}
		} 
	}

	@Override
	public void mouseReleased(GSMouseEvent e, LineSegEditorCanvas canvas) {
		if (this.editingLineSegForEdit == null || this.editingVertexOrLineViewIndex == -1 || this.editingLineSegForEditIndex == -1) return;
		
		LineSegmentForEdit line = new LineSegmentForEdit(this.editor, this.editingVertexPoints, this.editingLineSegForEdit.getLineDirection());
		this.editingLineSegForEdit.setVisible();
		this.editor.pushUndoStack();
		this.linesForEdit.remove(this.editingLineSegForEditIndex);
		this.linesForEdit.add(this.editingLineSegForEditIndex,line);
		this.editor.setLinesForEdit(this.linesForEdit);
		reset();
	}

	private void reset() {
		this.startingPoint  = null;
		this.line_editing_mode = false;
		this.editingLineSegForEdit = null;
		this.editingVertexOrLineViewIndex = -1;
		this.editingLineSegForEditIndex = -1;
		this.editingVertexOrLineViewIndex = -1;
		this.editingVertexPoints = null;
	}

	@Override
	public void mouseDragged(GSMouseEvent e, LineSegEditorCanvas canvas) {
		if (this.editingLineSegForEdit == null) {
			if (this.editingVertexOrLineViewIndex != -1) {
				log.debug("In LineSegEditorEditVertexMode, this.editingLineSegForEdit == null && this.editingVertexIndex != -1");
			}
			return;
		}
		if (this.editingVertexOrLineViewIndex == -1) {
			return;
		}
		
		if (! line_editing_mode) {
			Point2D point = this.editingVertexPoints.get(this.editingVertexOrLineViewIndex);
			point.setLocation(e.getPoint2D());
			this.editingLineSegForEdit.setInvisible();
		} else {
			if (this.editingVertexPoints == null) return;
			int size = this.editingVertexPoints.size();
			Point2D start = this.editingVertexPoints.get(this.editingVertexOrLineViewIndex);
			Point2D end;
			if (this.editingVertexOrLineViewIndex == size-1) end = this.editingVertexPoints.get(0);
			else end = this.editingVertexPoints.get(this.editingVertexOrLineViewIndex+1);
			Point2D current_point = e.getPoint2D();
			double x_diff  = current_point.getX() - this.startingPoint.getX();
			double y_diff  = current_point.getY() - this.startingPoint.getY();
			start.setLocation(this.start_in_original_position_x+x_diff,this.start_in_original_position_y+y_diff);
			end.setLocation(this.end_in_original_position_x+x_diff,this.end_in_original_position_y+y_diff);
			this.editingLineSegForEdit.setInvisible();
		}
	}

	@Override
	public void mouseMoved(GSMouseEvent e, LineSegEditorCanvas canvas) {
		// NOP
	}

	@Override
	public void paint(Graphics g, LineSegEditorCanvas canvas) {
		double ratio = canvas.getLineSegEditorImageLabel().getRatio();
		double gapX = canvas.getLineSegEditorImageLabel().getGapWidth();
		double gapY = canvas.getLineSegEditorImageLabel().getGapHeight();

		if (this.editingVertexPoints == null) return;
		
		for(int i=0;i<this.editingVertexPoints.size();i++){
			Point2D start = this.editingVertexPoints.get(i);
			Point2D end;
			if (i == this.editingVertexPoints.size()-1) {
				end = this.editingVertexPoints.get(0);
			} else {
				end = this.editingVertexPoints.get(i+1);
			}
			new LineView(new ExLine2D(start,end),true).enlargedView(ratio,gapX,gapY).draw((Graphics2D)g);
		}
		
		setCursor(canvas);
	}

	private void setCursor(LineSegEditorCanvas canvas) {
		canvas.getLineSegEditorImageLabel().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));	
	}

	@Override
	public void cancel() {
		// TODO Auto-generated method stub

	}

	@Override
	public void setParentLinesegEditor() {
		// TODO Auto-generated method stub

	}

	@Override
	public LineSegEditor getParentLinesegEditor() {
		// TODO Auto-generated method stub
		return null;
	}

}
