package smart_gs.drawing_tool;

import java.awt.Cursor;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import smart_gs.GSConstants;
import smart_gs.drawing_tool.drawing_mode.LineSegEditorDrawPolygonMode;
import smart_gs.drawing_tool.drawing_mode.LineSegEditorMode;
import smart_gs.drawing_tool.drawing_mode.LineSegEditorSelectMode;
import smart_gs.drawing_tool.view.LineSegView;
import smart_gs.drawing_tool.view.View;
import smart_gs.logical.LineSegment;
import smart_gs.logical.LineSegmentForEdit;
import smart_gs.logical.Preference;
import smart_gs.logical.Spread;
import smart_gs.smleditor.swingui.LineSegEditor;
import smart_gs.smleditor.swingui.LineSegFileType;
import smart_gs.swingui.GSMouseEvent;
import smart_gs.swingui.WorkspaceWindow;
import smart_gs.util.GSLog;
import smart_gs.util.Pair;
import smart_gs.util.SegfoToLineConverter;
import smart_gs.util.XMLToLineConverter;
import sml_editor.logical.LineDirection;

@SuppressWarnings("serial")
public class LineSegEditorImageLabel
extends JLabel
implements MouseListener, MouseMotionListener {

	GSLog log = GSLog.getInstance();
	private final int DEFAULT_WIDTH = 1200;
	private final int ZOOM_VALUE = 200;
	//20080617 shimizu wrote
	//private final int ZOOM_VALUE = 400;

	public int scale = 0;

	private List<LineSegmentForEdit> lines = new ArrayList<LineSegmentForEdit>();

	private LineSegFileType lineSegFileType;

	private LineSegEditorMode mode;
	private Cursor cursor = Cursor.getDefaultCursor();

	public void setLineSegEditorImageLabelCursor(Cursor cursor) {
		this.cursor = cursor;
		this.setCursor(this.cursor);
	}

	public enum Show{LINE_AND_INDEX,LINE};
	public Show lineSegShowingMode = Show.LINE_AND_INDEX;

	private LineSegEditorCanvas canvas;
	private ImageIcon imageIcon;

	private int currentWidth;
	private int currentHeight;

	private int imageWidth;
	private int imageHeight;
	private Image image;

	private List<View> tempViews;

	// HERE HERE refactor these.
	private Boolean canceled = false;

	public boolean isCanceled() {
		return canceled;
	}

	public void setCanceled(boolean canceled) {
		this.canceled = canceled;
	}


	public LineSegEditorImageLabel(LineSegEditorCanvas canvas, ImageIcon imageIcon,LineSegEditor editor) {
		super(imageIcon);
		this.mode = new LineSegEditorSelectMode(editor);
		this.canvas = canvas;
		this.imageIcon = imageIcon;
		this.setImageWidth(this.imageIcon.getIconWidth());
		this.setImageHeight(this.imageIcon.getIconHeight());
		this.image = this.imageIcon.getImage();
		this.tempViews = new ArrayList<View>();
		this.addMouseListener(this);
		this.addMouseMotionListener(this);		
		this.lines = this.loadLinesForEditFromLineSegInformationFile();
		if (this.lines == null) {
			log.info("Line Editor Canceled.");
			return;
		}
		this.showImage();
		this.setLayout(new FlowLayout(FlowLayout.LEFT));
		if (cursor != null) {
			this.setCursor(cursor);
		}
	}

	public LineSegEditorImageLabel(LineSegEditorCanvas canvas, ImageIcon imageIcon,LineSegEditor editor,List<LineSegmentForEdit> lines) {
		super(imageIcon);
		this.mode = new LineSegEditorSelectMode(editor);
		this.canvas = canvas;
		this.imageIcon = imageIcon;
		this.setImageWidth(this.imageIcon.getIconWidth());
		this.setImageHeight(this.imageIcon.getIconHeight());
		this.image = this.imageIcon.getImage();
		this.tempViews = new ArrayList<View>();
		this.addMouseListener(this);
		this.addMouseMotionListener(this);		
		this.lines = lines;
		this.showImage();
		this.setLayout(new FlowLayout(FlowLayout.LEFT));
		if (cursor != null) {
			this.setCursor(cursor);
		}
	}
	
	public void resetLineSegEditorImageLabel(ImageIcon imageIcon,List<LineSegmentForEdit> lines) {
		this.imageIcon = imageIcon;
		this.setImageWidth(this.imageIcon.getIconWidth());
		this.setImageHeight(this.imageIcon.getIconHeight());
		this.image = this.imageIcon.getImage();
		this.lines = lines;
		this.showImage();
		if (cursor != null) {
			this.setCursor(cursor);
		}
		this.repaint();
	}

	public void showImage() {
		double ratio = (double) getImageWidth() / (double) getImageHeight();
		int w = DEFAULT_WIDTH + scale * ZOOM_VALUE;
		this.setCurrentWidth(w);
		this.setCurrentHeight((int) (w / ratio));
		BufferedImage bufImage = new BufferedImage(this.getCurrentWidth(),
				this.getCurrentHeight(), BufferedImage.TYPE_INT_RGB);
		Graphics offg = bufImage.getGraphics();
		offg
		.drawImage(image, 0, 0, this.getCurrentWidth(), this.getCurrentHeight(),
				this);
		this.setSize(this.getCurrentWidth(), this.getCurrentHeight());
		this.setIcon(new ImageIcon(bufImage));
		WorkspaceWindow.getInstance().getImageToolBar().enableIcons();
	}

	public Point getCornerPoint() {
		return this.getLocation();
	}

	public void zoomIn() {
		// 2007/11/19 kazuhiro kobayashi
		if (this.scale < 21) {
			this.scale++;
			this.showImage();
		}
	}

	public void zoomOut() {
		// 2007/11/19 kazuhiro kobayashi
		if (this.scale > -5) {
			this.scale--;
			this.showImage();
		}
	}

	public void fullSize() {
		this.scale = 0;
		this.showImage();
	}

	public void fitWidth(int width) {
		this.scale = (width - DEFAULT_WIDTH) / ZOOM_VALUE - 1;
		this.showImage();
	}

	public void fitHeight(int height) {
		double ratio = (double) getImageWidth() / (double) getImageHeight();
		this.scale = (int) ((ratio * (double) height - DEFAULT_WIDTH) / ZOOM_VALUE - 1);
		this.showImage();
	}
	public void setScale(int scale) {
		this.scale = scale;
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);

		int width = this.canvas.getImageIcon().getIconWidth();
		int w = DEFAULT_WIDTH + scale * ZOOM_VALUE;
		double ratio = (double) ((double) w / (double) width);

		int canvasWidth = this.canvas.getWidth();
		int canvasHeight = this.canvas.getHeight();
		int iconHeight = this.getIcon().getIconHeight();
		int iconWidth = this.getIcon().getIconWidth();

		int scrollHeight = this.canvas.getHorizontalScrollBar().getHeight();
		int scrollWidth = this.canvas.getVerticalScrollBar().getWidth();
		double gapX = 0;
		double gapY = 0;

		if (iconWidth < canvasWidth) {
			gapX = (canvasWidth - scrollWidth - iconWidth) / 2.0;
		}
		if (iconHeight < canvasHeight) {
			gapY = (canvasHeight - scrollHeight - iconHeight) / 2.0;
		}

		if (lines != null ) {
			for (int i = 0; i < lines.size(); i++) {
				LineSegmentForEdit current_line = lines.get(i);
				LineSegView view = (LineSegView) current_line.getView().enlargedView(ratio,gapX,gapY);

				if (getSelectedLineIndexes().contains(i)) {
					view.setIsSelected(true);
				}
				else {
					view.setIsSelected(false);
				}
			}
			for (int i = 0; i < lines.size(); i++) {
				if (! lines.get(i).isSelected()) {
					LineSegmentForEdit current_line = lines.get(i);
					if (current_line.isVisible()) {
						LineSegView view = (LineSegView) current_line.getView().enlargedView(ratio,gapX,gapY);
						view.drawShapeForLineSegEditorImageLabel((Graphics2D) g, lineSegShowingMode, mode);
					}
				}
			}
			for (int i = 0; i < lines.size(); i++) {
				if (lines.get(i).isSelected()) {
					LineSegmentForEdit current_line = lines.get(i);
					if (current_line.isVisible()) {
						LineSegView view = (LineSegView) current_line.getView().enlargedView(ratio,gapX,gapY);
						view.drawShapeForLineSegEditorImageLabel((Graphics2D) g, lineSegShowingMode, mode);
					}
				}
			}
		}
		// This prints the incomplete line segment drawn.
		this.mode.paint(g, canvas); 
	}

	public double getGapWidth() {
		int canvasWidth = this.canvas.getWidth();
		int iconWidth = this.getIcon().getIconWidth();
		int scrollWidth = this.canvas.getVerticalScrollBar().getWidth();
		double gapX = 0;
		if (iconWidth < canvasWidth) {
			gapX = (canvasWidth - scrollWidth - iconWidth) / 2.0;
		}
		return gapX;
	}

	public double getGapHeight() {
		int canvasHeight = this.canvas.getHeight();
		int iconHeight = this.getIcon().getIconHeight();
		int scrollHeight = this.canvas.getHorizontalScrollBar().getHeight();
		double gapY = 0;
		if (iconHeight < canvasHeight) {
			gapY = (canvasHeight - scrollHeight - iconHeight) / 2.0;
		}
		return gapY;
	}

	public double getRatio() {
		int width = this.canvas.getImageIcon().getIconWidth();
		int w = DEFAULT_WIDTH + scale * ZOOM_VALUE;
		double ratio = ((double) ((double) w / (double) width));
		return ratio;
	}


	/**
	 *
	 * @param point
	 *
	 * @return
	 */
	public Point getAdjustedPoint(Point point) {
		double x = point.x;
		double y = point.y;
		int canvasWidth = this.canvas.getWidth();
		int canvasHeight = this.canvas.getHeight();
		int iconHeight = this.getIcon().getIconHeight();
		int iconWidth = this.getIcon().getIconWidth();

		double gapX = 0;
		double gapY = 0;
		int scrollHeight = this.canvas.getHorizontalScrollBar().getHeight();
		int scrollWidth = this.canvas.getVerticalScrollBar().getWidth();
		if (iconWidth < canvasWidth) {
			gapX = (double) (canvasWidth - scrollWidth - iconWidth) / 2.0;

		}
		if (iconHeight < canvasHeight) {
			gapY = (canvasHeight - scrollHeight - iconHeight) / 2.0;

		}

		x -= gapX;
		y -= gapY;

		int width = this.canvas.getImageIcon().getIconWidth();
		int w = iconWidth;
		double ratio = (double) ((double) w / (double) width);
		Point p = new Point((int) (x / ratio), (int) (y / ratio));
		return p;
	}

	private GSMouseEvent getAdjustedMouseEvent(MouseEvent e) {
		Point p = this.getAdjustedPoint(e.getPoint());
		return new GSMouseEvent(e, p);
	}

	public void mouseDragged(MouseEvent e) {
		GSMouseEvent ge = this.getAdjustedMouseEvent(e);
		this.mode.mouseDragged(ge, canvas);
		this.repaint();
	}

	public void mouseMoved(MouseEvent e) {
		GSMouseEvent ge = this.getAdjustedMouseEvent(e);
		this.mode.mouseMoved(ge, canvas);
		this.repaint();
	}

	public void mouseClicked(MouseEvent e) {
		Cursor cursor = this.getCursor();
		GSMouseEvent ge = this.getAdjustedMouseEvent(e);
		this.mode.mouseClicked(ge, canvas);
		this.repaint();
		this.setCursor(cursor);
	}

	public void mouseEntered(MouseEvent e) {
		GSMouseEvent ge = this.getAdjustedMouseEvent(e);
		this.setCursor(this.cursor);
		this.mode.mouseEntered(ge, canvas);
		this.repaint();
	}

	public void mouseExited(MouseEvent e) {
		GSMouseEvent ge = this.getAdjustedMouseEvent(e);
		this.mode.mouseExited(ge, canvas);
		this.repaint();
	}

	public void mousePressed(MouseEvent e) {
//		this.start = e.getPoint();
		//
		this.tempViews.clear();
		GSMouseEvent ge = this.getAdjustedMouseEvent(e);
		this.mode.mousePressed(ge, canvas);
		this.repaint();
	}

	public void mouseReleased(MouseEvent e) {
		this.setCursor(this.cursor);
//		this.start = null;
//		this.end = null;
		//
		GSMouseEvent ge = this.getAdjustedMouseEvent(e);
		this.mode.mouseReleased(ge, canvas);
		this.repaint();
	}

	/**
	 *
	 * @param point
	 *            original
	 */
	public void setCenterLocation(Point point) {
		double ratio = this.getRatio();
		int x = (int) (point.x * ratio);
		int y = (int) (point.x * ratio);
		this.setLocation(-x + 100, -y + 100);
	}

	//2011/01/25/ kukita
	public void moveTo(Point point) {
		int locX = (int)this.getLocation().getX();
		int locY = (int)this.getLocation().getY();

		int cw = this.canvas.getWidth();
		int ch = this.canvas.getHeight();
		int sh = this.canvas.getHorizontalScrollBar().getHeight();
		int sw = this.canvas.getVerticalScrollBar().getWidth();
		int iw = this.getIcon().getIconWidth();
		int ih = this.getIcon().getIconHeight();
		int x;
		int y;
		double ratio = this.getRatio();
		if (iw < cw) {
			x = locX;
		} else {
			x = (int) (cw/2 - (point.x * ratio));
			if (x > 0) {
				x = 0;
			} else if (x < cw - (sw + iw)) {
				x = cw - (sw + iw);
			}
		}
		if (ih < ch) {
			y = locY;
		} else {
			y = (int) (ch/2 - (point.y * ratio));
			if (y > 0) {
				y = 0;
			} else if (y < ch - (sh + ih)) {
				y = ch - (sh + ih);
			}
		}

		int hscroll = 0;
		while (!neighbor((int)this.getLocation().getX(),x)) {
			hscroll++;
			this.canvas.getHorizontalScrollBar().setValue(hscroll);
		}
		int vscroll = 0;
		while (!neighbor((int)this.getLocation().getY(),y)) {
			vscroll++;
			this.canvas.getVerticalScrollBar().setValue(vscroll);
		}

	}

	private static boolean neighbor(int n, int m) {
		return ((n - m) * (n - m)) <=1 ;
	}


	public void addTempView(View view) {
		this.tempViews.add(view);
	}

	/**
	 *
	 *
	 * @return
	 */
	public Spread getSpread() {
		return this.canvas.getSpread();
	}

	public void setCurrentWidth(int currentWidth) {
		this.currentWidth = currentWidth;
	}

	public int getCurrentWidth() {
		return currentWidth;
	}

	public void setCurrentHeight(int currentHeight) {
		this.currentHeight = currentHeight;
	}

	public int getCurrentHeight() {
		return currentHeight;
	}

	public void setImageWidth(int imageWidth) {
		this.imageWidth = imageWidth;
	}

	public int getImageWidth() {
		return imageWidth;
	}

	public void setImageHeight(int imageHeight) {
		this.imageHeight = imageHeight;
	}

	public int getImageHeight() {
		return imageHeight;
	}

	//	public LineSegEditorState getState() {
	//		return state;
	//	}
	//
	//	public void setState(LineSegEditorState state){
	//		this.state = state;
	//	}

	public void setMode(LineSegEditorMode mode){
		this.mode = mode;
		//		this.state.setMode(mode);
	}

	public Show getLineSegShowingMode() {
		return lineSegShowingMode;
	}
	public void setLineSegShowingMode(Show mode) {
		lineSegShowingMode = mode;
	}

	public void stepLineSegShowingMode(Show mode) {
		if (mode == Show.LINE_AND_INDEX) {
			lineSegShowingMode = Show.LINE;
		} else if (mode == Show.LINE) {
			lineSegShowingMode = Show.LINE_AND_INDEX;
		}
	}


	public void rewriteLinesSegIndexes() {
		for (int i = 0; i<lines.size();i++){
			lines.get(i).setIndex(i);
		}
	}


	public void addLineSegmentForEdit(LineSegmentForEdit LineSegmentForEdit) {
		this.lines.add(LineSegmentForEdit);
	}

	/**
	 * @return the lines
	 */
	public List<LineSegmentForEdit> getLines() {
		return this.lines;
	}

	public List<LineSegmentForEdit> loadLinesForEditFromLineSegInformationFile() {
		String imageFileNameInNativeCode =  this.canvas.getSpread().getFileName();
		String filenameBody = Preference.getInstance().getDscFolderPathString() + this.canvas.getSpread().getSpreadDirParent().getPath()
				+ this.canvas.getSpread().getFileNameWithoutExtension();

		String filenameXmlInNativeCoding = filenameBody + ".xml";
		File fileXml = new File(filenameXmlInNativeCoding);
		String filenameSegfoInNativeCoding = filenameBody + ".segfo";
		File fileSegfo = new File(filenameSegfoInNativeCoding);
		//		String filename;
		Pair<LineDirection,List<LineSegmentForEdit>> answerPair = null;

		if (fileXml.exists()) {
			answerPair = new XMLToLineConverter(this.canvas.getSpread()).getLinesForEdit(fileXml);
			if (answerPair == null) {
				log.error("Failed to read line segment file %s" + filenameXmlInNativeCoding);
				return null;
			}
			lineSegFileType = LineSegFileType.XML;
			//			filename = filenameXmlInNativeCoding;
			return answerPair.getRight();

		} else if (fileSegfo.exists()) {
			answerPair = new SegfoToLineConverter(this.canvas.getSpread()).getLinesForEdit(fileSegfo,filenameSegfoInNativeCoding);
			if (answerPair == null) {
				log.error("Failed to read line segment file " + filenameSegfoInNativeCoding);
				return null;
			}
			lineSegFileType = LineSegFileType.SEGFO;
			//			filename = filenameSegfoInNativeCoding;
			return answerPair.getRight();
		} else { // Set line direction of LineSeg Editor and returns the empy Line Seg Info List.
			String[] possibilities;
			String[] possibilitiesHorizontal = {"horizontal", "vertical"};
			String[] possibilitiesVertical = {"vertical", "horizontal"};
			if (Preference.getInstance().getDefaultLineDirection() == GSConstants.TEXT_TYPE_VERTICAL) {
				possibilities = possibilitiesVertical;
			} else {
				possibilities = possibilitiesHorizontal;
			}

			String s = (String)JOptionPane.showInputDialog(
					this,
					"No line info. file. Creating line info.\n" +
							"Choose line direction type.",
							"Creating Line Info.",
							JOptionPane.PLAIN_MESSAGE,
							null,
							possibilities,
					LineSegEditor.line_direction);
			if (s == null) {
				setCanceled(true);
				return null;
			} else {
				if (s.equals("horizontal") || s.equals("vertical")) {
					LineDirection ld = s.equals("horizontal")?LineDirection.HORIZONTAL:LineDirection.VERTICAL;
					LineSegEditor.line_direction = ld;
					this.canvas.getSpread().setLineDirection(ld);
					return new ArrayList<LineSegmentForEdit>();
				} else {
					GSLog.getInstance().debug("Bug in LineSegEditorImageLabel.java (1)");
					return null;
				}
			}
		}
	}


	/**
	 * @param lines the lines to set
	 */
	public void setLines(List<LineSegmentForEdit> lines) {
		//this.lines = lines;	// mod 20120923 yamanaka
		if (lines != null) {
			this.lines = lines;
		} else {
			this.lines = new ArrayList<LineSegmentForEdit>();
		}
	}

	public int getLineIndex(int x, int y) {
		if (lines != null) {
			for (int i = 0; i < lines.size(); i++) {
				LineSegView view = (LineSegView) lines.get(i).getView();
				if (view.contains(new Point(x,y))) return i;
			}
		}
		return -1; /* not found */
	}

	public LineSegEditorMode getMode() {
		return this.mode;
	}

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

	public int getSelectedLineIndex() {
		int ans = -1;
		for (int i=0;i<lines.size();i++) {
			if (lines.get(i).isSelected()) {
				ans = i;
				break;
			}
		}
		return ans;
	}

	public ArrayList<Integer> getSelectedLineIndexes() {
		ArrayList<Integer> ans = new ArrayList<Integer>();
		for (int i=0;i<lines.size();i++) {
			if (lines.get(i).isSelected()) {
				ans.add(new Integer(i));
			}
		}
		return ans;
	}


	public List<LineSegmentForEdit> getSelectedLines() {
		ArrayList<LineSegmentForEdit> ans = new ArrayList<LineSegmentForEdit>();
		for (int i=0;i<lines.size();i++) {
			if (lines.get(i).isSelected()) {
				ans.add(lines.get(i));
			}
		}
		return ans;
	}

	public void resetSelectionOfLines() {
		for (int i=0; i< lines.size(); i++) {
			lines.get(i).setIsSelected(false);
		}
	}

	public boolean isSelected(int i) {
		return lines.get(i).isSelected();
	}

	public void setIsSelected(int i, boolean b) {	
		lines.get(i).setIsSelected(b);
	}

	public void addSelectedRegionOfIndexes(int index) {
		int size = lines.size();
		int min=size-1, max=0;
		if (size==0) lines.get(index).setIsSelected(true);
		ArrayList<Integer> selectedIndexes = getSelectedLineIndexes();
		for (int i=0;i<size;i++) {
			if (selectedIndexes.contains(i)) {
				if (i > max) max = i;
				if (i < min) min = i;
			}
		}

		if (index < min) {
			resetSelectionOfLines();
			for (int i = index; i <= min; i++) setIsSelected(i,true);
		}
		if (index > max) {
			resetSelectionOfLines();
			for (int i = max; i <= index; i++) setIsSelected(i,true);
		}
	}

	public LineSegFileType getLineSegFileType() {
		return lineSegFileType;
	}

	public void setLineSegFileType(LineSegFileType lineSegFileType) {
		this.lineSegFileType = lineSegFileType;
	}
}
