package org.spview.gui;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import org.spview.point.ResidualPoint;

public class PlotData extends JPanel {

	/**
	 * 
	 */
	private static final long serialVersionUID = -2309468385394812985L;
	private final int padding = 50;
	private final int labelPadding = 25;
	private final Color lineColor = new Color(44, 102, 230, 180);
	private final Color pointColor = new Color(100, 100, 100, 180);
	private final Color gridColor = new Color(200, 200, 200, 200);
	private static final Stroke GRAPH_STROKE = new BasicStroke(2f);
	final private int accuracyRadius = 12;
	private final List<ResidualPoint> data;
	private JFText jft;

	public PlotData(JobPlay jobplay, List<ResidualPoint> data) {

		addMouseListener(new MouseAdapter() {
			@Override
			public void mouseEntered(MouseEvent me) {
				setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
			}

			@Override
			public void mouseExited(MouseEvent me) {
				setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
			}

			@Override
			public void mouseClicked(MouseEvent me) {
				if (!me.isConsumed()) {
					if (me.getClickCount() == 2) { // handle double click
						me.consume();
						Point2D point = me.getPoint();

						// get Min and Max;
						double maxX = getRealX(point.getX() + accuracyRadius / 2.0);
						double minX = getRealX(point.getX() - accuracyRadius / 2.0);
						jobplay.getPanAff().setXZoom(minX, maxX);
						jobplay.setUnsave(true);
					} else {
						Point2D point = me.getPoint();
						ArrayList<String> result = findPoint(point);
						if (result.size() > 0) {
							showPoint(result);
						}
					}
				}
			}

		});
		this.data = data;
	}
	
	private double getXScale() {
		return ((double) getWidth() - (2 * padding) - labelPadding) / (getMaxXData() - getMinXData());
	}
	
	private double getYScale() {
		return ((double) getHeight() - (2 * padding) - labelPadding) / (getMaxYData() - getMinYData());
	}

	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

		List<Point> graphPoints = new ArrayList<>();
		for (ResidualPoint datum : data) {
			int x1 = (int) ((double) getWidth() - padding - (getMaxXData() - datum.getX()) * getXScale());
			int y1 = (int) ((getMaxYData() - datum.getY()) * getYScale() + padding);
			graphPoints.add(new Point(x1, y1));
		}

		// draw white background
		g2.setColor(Color.WHITE);
		g2.fillRect(padding + labelPadding, padding, getWidth() - (2 * padding) - labelPadding,
				getHeight() - 2 * padding - labelPadding);
		g2.setColor(Color.BLACK);

		// create hatch marks and grid lines for y axis.
		int pointWidth = 4;
		int numberYDivisions = 10;
		for (int i = 0; i < numberYDivisions + 1; i++) {
			int x0 = padding + labelPadding;
			// int x1 = pointWidth + padding + labelPadding;
			int y0 = getHeight()
					- ((i * (getHeight() - padding * 2 - labelPadding)) / numberYDivisions + padding + labelPadding);

			if (data.size() > 0) {
				g2.setColor(gridColor);
				g2.drawLine(padding + labelPadding + 1 + pointWidth, y0, getWidth() - padding, y0);
				g2.setColor(Color.BLACK);
				double yLabelDouble = ((int) ((getMinYData()
						+ (getMaxYData() - getMinYData()) * ((i * 1.0) / numberYDivisions)) * 100)) / 100.0;
				String yLabel = String.format("%.2f", yLabelDouble);
				FontMetrics metrics = g2.getFontMetrics();
				int labelWidth = metrics.stringWidth(yLabel);
				g2.drawString(yLabel, x0 - labelWidth - 5, y0 + (metrics.getHeight() / 2) - 3);
			}
			// g2.drawLine(x0, y0, x1, y1);
		}

		// and for x axis
		for (int i = 0; i < data.size(); i++) {
			if (data.size() > 1) {
				int x0 = i * (getWidth() - padding * 2 - labelPadding) / (data.size() - 1) + padding + labelPadding;
				int y0 = getHeight() - padding - labelPadding;
//				int y1 = y0 - pointWidth;
				if ((i % ((int) ((data.size() / 3.0)) + 1)) == 0) {
					g2.setColor(gridColor);
					g2.drawLine(x0, getHeight() - padding - labelPadding - 1 - pointWidth, x0, padding);
					g2.setColor(Color.BLACK);
					String xLabel = String.format("%.3f", data.get(i).getX());
					FontMetrics metrics = g2.getFontMetrics();
					int labelWidth = metrics.stringWidth(xLabel);
					g2.drawString(xLabel, x0 - labelWidth / 2, y0 + metrics.getHeight() + 3);
				} else if (i == data.size() - 1) {
					String xLabel = String.format("%.3f", data.get(i).getX());
					FontMetrics metrics = g2.getFontMetrics();
					int labelWidth = metrics.stringWidth(xLabel);
					g2.drawString(xLabel, x0 - labelWidth / 2, y0 + metrics.getHeight() + 3);
				}
				// g2.drawLine(x0, y0, x1, y1);
			}
		}

		// create x and y axes
		g2.drawLine(padding + labelPadding, getHeight() - padding - labelPadding, padding + labelPadding, padding);
		g2.drawLine(padding + labelPadding, getHeight() - padding - labelPadding, getWidth() - padding,
				getHeight() - padding - labelPadding);

		Stroke oldStroke = g2.getStroke();
		g2.setColor(lineColor);
		g2.setStroke(GRAPH_STROKE);

		g2.setStroke(oldStroke);
		g2.setColor(pointColor);
		for (Point graphPoint : graphPoints) {
			int x = graphPoint.x - pointWidth / 2;
			int y = graphPoint.y - pointWidth / 2;
			g2.fillOval(x, y, pointWidth, pointWidth);
		}
	}

	private void showPoint(ArrayList<String> result) {
		if (jft != null) {
			// free old frame
			jft.dispose();
		}
		jft = new JFText(result, 0, 50);
		jft.setVisible(true);
	}

	private ArrayList<String> findPoint(Point2D mouse) {
		ArrayList<String> bingo = new ArrayList<>();

		for (ResidualPoint point : data) {
			Point pxPoint = new Point(getPixelX(point.getX()), getPixelY(point.getY()));
			double dist = pxPoint.distance(mouse.getX(), mouse.getY());
			if (dist < accuracyRadius) {
				bingo.add(point.toString());
			}
		}
		return bingo;
	}

	public double getRealX(double x) {
		return ((x - (double) getWidth() + padding) / getXScale()) + getMaxXData();
	}
	
	public int getPixelX(double x) {
		return (int) ((double) getWidth() - padding - (getMaxXData() - x) * getXScale());
	}
	
	public int getPixelY(double y) {
		return (int) ((getMaxYData() - y) * getYScale() + padding);
	}

	private double getMinYData() {
		double minData = Double.MAX_VALUE;
		for (ResidualPoint point : data) {
			minData = Math.min(minData, point.getY());
		}
		return minData;
	}

	private double getMaxYData() {
		double maxData = Double.MIN_VALUE;
		for (ResidualPoint point : data) {
			maxData = Math.max(maxData, point.getY());
		}
		return maxData;
	}

	private double getMinXData() {
		double minData = Double.MAX_VALUE;
		for (ResidualPoint point : data) {
			minData = Math.min(minData, point.getX());
		}
		return minData;
	}

	private double getMaxXData() {
		double maxData = Double.MIN_VALUE;
		for (ResidualPoint point : data) {
			maxData = Math.max(maxData, point.getX());
		}
		return maxData;
	}

	public List<ResidualPoint> getData() {
		return data;
	}
	
	public JFText getJFT() {
		return jft;
	}

}