package jp.ehobby.ui.gui.graph.view;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;

import javax.swing.JPanel;

/**
 * グラフView.
 * @author kei-n
 */
public abstract class AbstractGraphView extends JPanel {

	/** 生成シリアルバージョンUID.	*/
	private static final long serialVersionUID = 6257995251078151973L;

	/** マージン 上.				*/
	private static final int MARGIN_TOP    = 0;

	/** マージン 右.				*/
	private static final int MARGIN_RIGHT  = 1;

	/** マージン 下.				*/
	private static final int MARGIN_BOTTOM = 2;

	/** マージン 左.				*/
	private static final int MARGIN_LEFT   = 3;


	/** グラフ描画エリア.	*/
	protected Rectangle graphArea;

	/** グラフスケール.		*/
	protected Rectangle graphScale;

	/** グラフ領域の背景色.	*/
	protected Color bgColor;

	/** グラフ領域マージン.	*/
	protected int[] margin = { 0, 0, 0, 0 };


	/**
	 * 初期化.
	 */
	public AbstractGraphView() {
		this.bgColor    = Color.gray;
		this.graphArea  = new Rectangle();
		this.graphScale = new Rectangle();
	}


	/**
	 * グラフを描画する.
	 *
	 * @param g Graphics2D
	 */
	public abstract void paintGraph(final Graphics2D g);


	/**
	 * グラフ背景色を設定します.
	 *
	 * @param color 背景色
	 */
	public void setBackgroundColor(final Color color) {
		this.bgColor = color;
	}


	/**
	 * グラフ領域マージン.
	 * @param m マージン
	 */
	public void setMargin(final int[] m) {
		this.margin = m;
	}


	/**
	 * グラフ描画エリアを設定します.
	 *
	 * @param rect 描画エリア
	 */
	public void setGraphArea(final Rectangle rect) {
		this.graphArea.x      = rect.x      + this.margin[MARGIN_LEFT];
		this.graphArea.y      = rect.y      + this.margin[MARGIN_TOP];
		this.graphArea.width  = rect.width  - (this.margin[MARGIN_LEFT] + this.margin[MARGIN_RIGHT]);
		this.graphArea.height = rect.height - (this.margin[MARGIN_TOP]  + this.margin[MARGIN_BOTTOM]);
	}


	/**
	 * グラフの座標指定用の値範囲を設定します.
	 *
	 * @param rect
	 */
	public void setGraphScale(final Rectangle rect) {
		this.graphScale = rect;
	}


	/**
	 * 描画時に実行されます.
	 *
	 * @param g Graphics
	 */
	@Override
	public final void paintComponent(Graphics g) {
		Rectangle nowRect = getBounds();
		setGraphArea(nowRect);
		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

		// 背景設定
		setBackground(g2);

		// 下位クラスの描画実施
		paintGraph(g2);
	}


	/**
	 * 背景を設定します.
	 *
	 * @param g Graphics2D
	 */
	public void setBackground(final Graphics2D g) {
		g.setColor(this.bgColor);
		g.fillRect(this.graphArea.x, this.graphArea.y, this.graphArea.width, this.graphArea.height);
		g.setColor(Color.BLACK);
		g.draw(new Rectangle2D.Double(this.graphArea.x, this.graphArea.y, this.graphArea.width, this.graphArea.height));
	}


	/**
	 * 指定された x, y 座標に点をプロットします.
	 *
	 * @param g Graphics2D
	 * @param x X座標
	 * @param y Y座標
	 */
	public void plot(final Graphics2D g, final double x, final double y) {
		double realX = toRealPointX(x);
		double realY = toRealPointY(y);
		g.draw(new Line2D.Double(realX, realY, realX, realY));
	}


	/**
	 * 線を描画します.
	 *
	 * @param g Graphics2D
	 * @param srcX  開始X座標
	 * @param srcY  開始Y座標
	 * @param destX 終了X座標
	 * @param destY 終了Y座標
	 */
	public void lineTo(final Graphics2D g, final double srcX, final double srcY,
											final double destX, final double destY) {
		double realSrcX  = toRealPointX(srcX);
		double realSrcY  = toRealPointY(srcY);
		double realDestX = toRealPointX(destX);
		double realDestY = toRealPointY(destY);
		g.draw(new Line2D.Double(realSrcX, realSrcY, realDestX, realDestY));
	}


	/**
	 * 矩形を塗りつぶします.
	 *
	 * @param g Graphics2D
	 * @param x  x座標
	 * @param y  y座標
	 * @param width 幅
	 * @param height 高さ
	 * @param fillColor   塗りつぶしの色
	 * @param borderColor 枠の色
	 */
	public void fillRect(final Graphics2D g,
			final double x, final double y, final double width, final double height,
			final Color fillColor, final Color borderColor) {
		double realX      = toRealPointX(x);
		double realY      = toRealPointY(y);
		double realWidth  = toRealWidth(width);
		double realHeight = toRealHeight(height);
		g.setColor(fillColor);
		g.fill(new Rectangle2D.Double(realX, (realY - realHeight), realWidth, realHeight));
		g.setColor(borderColor);
		g.draw(new Rectangle2D.Double(realX, (realY - realHeight), realWidth, realHeight));
	}

	/**
	 * 矩形を描きます.
	 *
	 * @param g Graphics2D
	 * @param x  x座標
	 * @param y  y座標
	 * @param width 幅
	 * @param height 高さ
	 * @param borderColor 枠の色
	 */
	public void drawRect(final Graphics2D g,
			final double x, final double y, final double width, final double height,
			final Color borderColor) {
		double realX      = toRealPointX(x);
		double realY      = toRealPointY(y);
		double realWidth  = toRealWidth(width);
		double realHeight = toRealHeight(height);
		g.setColor(borderColor);
		g.draw(new Rectangle2D.Double(realX, (realY - realHeight), realWidth, realHeight));
	}


	/**
	 * 指定されたグラフのX座標を実座標に変換します.
	 *
	 * @param x グラフ上のX座標
	 * @return 実座標
	 */
	protected double toRealPointX(final double x) {

		// graphScale 内にて 0 起点に変換
		// -100    0     100      0           200
		//   +-----+------+  -->  +-----+------+
		//           .(30)                .(130)
		double realX = x - this.graphScale.x;


		// スケールを合わせる
		//  10            110    10             110
		//   +-------------+  --> +-------------+
		//   0            200             .(10+75)
		//           .(130)
		realX = this.graphArea.x + realX * ((double) this.graphArea.width / (double) this.graphScale.width);

		return realX;
	}

	/**
	 * 指定されたグラフのY座標を実座標に変換します.
	 *
	 * @param y グラフ上のY座標
	 * @return 実座標
	 */
	protected double toRealPointY(final double y) {

		// graphScale 内にて 0 起点に変換 ※図は⇒が下
		// -100    0     100      0           200
		//   +-----+------+  -->  +-----+------+
		//           .(30)                .(130)
		double realY = y - this.graphScale.y;

		// スケールを合わせる ※図は⇒が下
		//  110           10     110            10
		//   +-------------+  --> +-------------+
		//   0            200             .(110-75)
		//           .(130)
		//
		realY = this.graphArea.height + this.graphArea.y - realY * ((double) this.graphArea.height / (double) this.graphScale.height);

		return realY;

	}


	/**
	 * 指定されたグラフ上の幅を実幅に変換します.
	 *
	 * @param width グラフ上の幅
	 * @return 実幅
	 */
	protected double toRealWidth(final double width) {
		double realWidth = width * ((double) this.graphArea.width / (double) this.graphScale.width);
		return realWidth;
	}


	/**
	 * 指定されたグラフ上の高さを実幅に変換します.
	 *
	 * @param height グラフ上の高さ
	 * @return 実高さ
	 */
	protected double toRealHeight(final double height) {
		double realHeight = height * ((double) this.graphArea.height / (double) this.graphScale.height);
		return realHeight;
	}

}
