/*
 * Copyright (c) 2006-2009 OrangeSignal.com All rights reserved.
 * 
 * これは Apache ライセンス Version 2.0 (以下、このライセンスと記述) に
 * 従っています。このライセンスに準拠する場合以外、このファイルを使用
 * してはなりません。このライセンスのコピーは以下から入手できます。
 * 
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 * 
 * 適用可能な法律がある、あるいは文書によって明記されている場合を除き、
 * このライセンスの下で配布されているソフトウェアは、明示的であるか暗黙の
 * うちであるかを問わず、「保証やあらゆる種類の条件を含んでおらず」、
 * 「あるがまま」の状態で提供されるものとします。
 * このライセンスが適用される特定の許諾と制限については、このライセンス
 * を参照してください。
 */

package jp.sf.orangesignal.chart.ui.canvas;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;

import jp.sf.orangesignal.chart.ChartSettings;
import jp.sf.orangesignal.chart.axis.NumberAxis;
import jp.sf.orangesignal.chart.axis.Orientation;
import jp.sf.orangesignal.chart.axis.NumberAxis.RangeType;
import jp.sf.orangesignal.chart.data.AntiWatchChartDataset;
import jp.sf.orangesignal.chart.event.ChartEvent;
import jp.sf.orangesignal.chart.event.ChartScreenEvent;
import jp.sf.orangesignal.chart.ui.Icons;
import jp.sf.orangesignal.chart.ui.UpDownColorType;
import jp.sf.orangesignal.chart.ui.screen.ChartScreen;
import jp.sf.orangesignal.chart.util.DrawUtils;
import jp.sf.orangesignal.chart.util.StringManager;

/**
 * 逆ウォッチ曲線のキャンバスを提供します。
 * 
 * @author 杉澤 浩二
 */
public class AntiWatchCanvas extends AbstractChartCanvas {

	private static final long serialVersionUID = -4579368515123910594L;

	// ---------------------------------------- 軸と領域

	/**
	 * 価格軸を保持します。
	 */
	private final NumberAxis priceAxis = new NumberAxis();

	/**
	 * 出来高軸を保持します。
	 */
	private final NumberAxis volumeAxis = new NumberAxis();

	/**
	 * チャート描画領域を保持します。
	 */
	private Rectangle2D chartArea = null;

	// ---------------------------------------- トレース

	/**
	 * チャート上のマウス座標を保持します。
	 */
	private Point mousePosition = null;

	/**
	 * チャート画面への参照を保持します。
	 */
	private final ChartScreen parent;

	// ---------------------------------------- データ

	/**
	 * データセットを保持します。
	 */
	private AntiWatchChartDataset dataset = null;

	/**
	 * 開始位置を保持します。
	 */
	private int start;

	/**
	 * 範囲を保持します。
	 */
	private int period;

	/**
	 * 設定情報を保持します。
	 */
	private ChartSettings settings;

	// ----------------------------------------

	/**
	 * コンストラクタです。
	 * 
	 * @param icons アイコン情報
	 * @param parent チャート画面への参照
	 */
	public AntiWatchCanvas(final Icons icons, final ChartScreen parent) {
		this.parent = parent;

		this.priceAxis.setRangeType(RangeType.POSITIVE);
		this.priceAxis.setLowerPadding(0.05);
		this.priceAxis.setUpperPadding(0.05);

		this.volumeAxis.setOrientation(Orientation.HORIZONTAL);
		this.volumeAxis.setRangeType(RangeType.POSITIVE);
		this.volumeAxis.setLowerPadding(0.05);
		this.volumeAxis.setUpperPadding(0.05);

		// 	デリゲーションイベントモデルでマウスイベントを処理します。

		addMouseListener(new MouseAdapter() {
			@Override
			public void mouseExited(final MouseEvent e) {
				processPosition(null, false);
				repaint();
			}
		});

		addMouseMotionListener(new MouseMotionAdapter() {
			@Override
			public void mouseMoved(final MouseEvent e) {
				processPosition(new Point(e.getX(), e.getY()), false);
				repaint();
			}
		});

		addComponentListener(new ComponentAdapter() {
			@Override
			public void componentResized(final ComponentEvent e) {
				processLayout();
			}
		});
	}

	private void processPosition(final Point point, final boolean send) {
		if (this.chartArea != null && point != null && this.chartArea.contains(point))
			this.mousePosition = point;
		else
			this.mousePosition = null;

		if (send)
			this.parent.sendPositionChanged(new ChartScreenEvent(this, this.mousePosition, this.start, this.period, -1));
	}

	/**
	 * データセットを設定します。
	 */
	@Override
	public void switchDataset(final ChartEvent e) {
		this.dataset = (AntiWatchChartDataset) e.getDataset();
		if (!e.isIgnoreStart())
			this.start = e.getStart();
		this.period = e.getPeriod();
		this.settings = e.getSettings();

		if (this.settings.antiwatch.fixed) {
			this.priceAxis.setFixedLower(new Double(0));
			this.volumeAxis.setFixedLower(new Double(0));
		} else {
			this.priceAxis.setFixedLower(null);
			this.volumeAxis.setFixedLower(null);
		}

		if (this.dataset != null) {
			this.priceAxis.prepare(new Number[][] { this.dataset.ma });
			this.volumeAxis.prepare(new Number[][] { this.dataset.vma });
		}

		adjustTicks();
		processLayout();
		processPosition(null, true);
		update();
	}

	/**
	 * 目盛りを調整します。
	 */
	private void adjustTicks() {
		if (this.dataset != null) {
			this.priceAxis.autoAdjustRange(this.start, this.period);
			this.volumeAxis.autoAdjustRange(this.start, this.period);
		}

		this.priceAxis.refreshTicks();
		this.volumeAxis.refreshTicks();
	}

	/**
	 * 時系列データの描画開始位置を設定します。
	 * 描画開始位置を設定すると描画範囲の計算も行われます。
	 * 
	 * @param start 描画開始位置
	 */
	@Override
	public void setStart(final int start) {
		this.start = start;
		update();
	}

	private void update() {
		adjustTicks();
		processPosition(null, true);
		this.screenCache = null;
		repaint();
	}

	/**
	 * 左右のマージンです。
	 */
	private static final int MARGIN = 6;

	/**
	 * レイアウトを調整します。
	 */
	private void processLayout() {
		final Graphics2D g2 = (Graphics2D) getGraphics();

		final int axisWidth = this.priceAxis.getSpace(g2);
		final int axisHeight = this.volumeAxis.getSpace(g2);

		final int x = MARGIN + axisWidth;
		final int w = getWidth() - x - MARGIN;
		final int h = getHeight() - axisHeight;

		final FontMetrics fm = g2.getFontMetrics(NumberAxis.FONT);
		final double plotMarginHeight = fm.getAscent() * 0.5 + fm.getDescent();

		this.chartArea = new Rectangle2D.Double(x, plotMarginHeight, w, h - plotMarginHeight);

		// ラベルの最大個数を算出
		this.priceAxis.refreshTicks(g2, this.chartArea);
		this.volumeAxis.refreshTicks(g2, this.chartArea);

		this.screenCache = null;
	}

	// ------------------------------------------------------------ 描画

	/**
	 * 画面の基礎イメージを保持します。
	 */
	private Image screenCache = null;

	/**
	 * 画面を描画します。
	 */
	@Override
	public void draw(final Graphics2D g2) {
		if (this.screenCache == null)
			this.screenCache = createImage();

		g2.drawImage(this.screenCache, 0, 0, this);

		if (this.settings.trace)
			drawAxisTrace(g2);
	}

	/**
	 * 画面の基礎イメージを作成して返します。
	 * 
	 * @return 画面の基礎イメージ
	 */
	private Image createImage() {
		final Image image = createImage(getWidth(), getHeight());
		final Graphics2D g2 = (Graphics2D) image.getGraphics();
		setRenderingHints(g2);

		// 縦目盛りと縦グリッド線を描画します。
		this.priceAxis.draw(g2, this.chartArea);
		this.volumeAxis.draw(g2, this.chartArea);

		// チャートを描画します。
		if (this.dataset != null)
			drawChart(g2);

		g2.dispose();
		return image;
	}

	/**
	 * トレ－ス目盛りを描画します。
	 * 
	 * @param g2
	 */
	private void drawAxisTrace(final Graphics2D g2) {
		if (this.mousePosition != null) {
			this.priceAxis.drawAxisTrace(g2, this.chartArea, this.mousePosition.y, 2);
			this.volumeAxis.drawAxisTrace(g2, this.chartArea, this.mousePosition.x, 2);
		}
	}

	/**
	 * チャートを描画します。
	 * 
	 * @param g2
	 */
	private void drawChart(final Graphics2D g2) {
		final Shape saved = g2.getClip();
		g2.setClip(this.chartArea);

		if (this.settings.antiwatch.signalMarker)
			drawOctagon(g2);

		drawLine(g2, this.dataset.ma, this.dataset.vma, Color.RED);
		DrawUtils.drawDescription(g2, this.chartArea, StringManager.getString("screen.antiwatch"));

		g2.setClip(saved);
	}

	/**
	 * 透明度 25% の Composite オブジェクトです。
	 */
	private static final Composite ALPHA_COMPOSITE_25 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.25F);

	/**
	 * 透明度 20% の Composite オブジェクトです。
	 */
	private static final Composite ALPHA_COMPOSITE_20 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.20F);

	/**
	 * 八角形のシグナルマーカーを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 */
	private void drawOctagon(final Graphics2D g2) {
		// 八角形の座標を算出します。
		final double x = this.chartArea.getCenterX();
		final double y = this.chartArea.getCenterY();
		final double w = (this.chartArea.getWidth() * 0.5) / Math.tan(Math.toRadians(90 - 45 * 0.5));
		double h = (this.chartArea.getWidth() * 0.5) * Math.tan(Math.toRadians(45 * 0.5));
		h = h / this.chartArea.getWidth() * this.chartArea.getHeight();

		// 各領域を描画します。
		final Composite originalComposite = g2.getComposite();
		final UpDownColorType colors = settings.updownLineColors;
		final Paint buyPaint = new GradientPaint(
				0, (float) this.chartArea.getMinY(), colors.getDownColor1(),
				0, (float) this.chartArea.getMaxY(), colors.getDownColor2());
		final Paint sell = new GradientPaint(
				0, (float) this.chartArea.getMinY(), colors.getUpColor2(),
				0, (float) this.chartArea.getMaxY(), colors.getUpColor1());

		// 領域1(陽転の兆し)を描画します。
		final GeneralPath path1 = new GeneralPath();
		path1.moveTo((float) x, (float) y);
		path1.lineTo((float) (x + w), (float) this.chartArea.getMaxY());
		path1.lineTo((float) (x - w), (float) this.chartArea.getMaxY());
		path1.closePath();
		g2.setPaint(buyPaint);
		g2.setComposite(ALPHA_COMPOSITE_25);
		g2.fill(path1);

		// 領域2(買い信号/買いシグナル)を描画します。
		final GeneralPath path2 = new GeneralPath();
		path2.moveTo((float) x, (float) y);
		path2.lineTo((float) this.chartArea.getMaxX(), (float) (y + h));
		path2.lineTo((float) this.chartArea.getMaxX(), (float) this.chartArea.getMaxY());	// XXX - 端まで塗りつぶさない場合はコメント化すること
		path2.lineTo((float) (x + w), (float) this.chartArea.getMaxY());
		path2.closePath();
		g2.setPaint(buyPaint);
		g2.setComposite(ALPHA_COMPOSITE_25);
		g2.fill(path2);

		// 領域3(押し目買い)を描画します。
		final GeneralPath path3 = new GeneralPath();
		path3.moveTo((float) x, (float) y);
		path3.lineTo((float) this.chartArea.getMaxX(), (float) (y - h));
		path3.lineTo((float) this.chartArea.getMaxX(), (float) (y + h));
		path3.closePath();
		g2.setPaint(buyPaint);
		g2.setComposite(ALPHA_COMPOSITE_25);
		g2.fill(path3);

		// 領域4(買い見送り/吹き値売り)を描画します。
		final GeneralPath path4 = new GeneralPath();
		path4.moveTo((float) x, (float) y);
		path4.lineTo((float) (x + w), (float) this.chartArea.getMinY());
		path4.lineTo((float) this.chartArea.getMaxX(), (float) this.chartArea.getMinY());	// XXX - 端まで塗りつぶさない場合はコメント化すること
		path4.lineTo((float) this.chartArea.getMaxX(), (float) (y - h));
		path4.closePath();
		g2.setPaint(buyPaint);
		g2.setComposite(ALPHA_COMPOSITE_20);
		g2.fill(path4);

		// 領域5(陰転の兆し)を描画します。
		final GeneralPath path5 = new GeneralPath();
		path5.moveTo((float) x, (float) y);
		path5.lineTo((float) (x - w), (float) this.chartArea.getMinY());
		path5.lineTo((float) (x + w), (float) this.chartArea.getMinY());
		path5.closePath();
		g2.setPaint(sell);
		g2.setComposite(ALPHA_COMPOSITE_25);
		g2.fill(path5);

		// 領域6(売り信号/売りシグナル)を描画します。
		final GeneralPath path6 = new GeneralPath();
		path6.moveTo((float) x, (float) y);
		path6.lineTo((float) this.chartArea.getMinX(), (float) (y - h));
		path6.lineTo((float) this.chartArea.getMinX(), (float) this.chartArea.getMinY());	// XXX - 端まで塗りつぶさない場合はコメント化すること
		path6.lineTo((float) (x - w), (float) this.chartArea.getMinY());
		path6.closePath();
		g2.setPaint(sell);
		g2.setComposite(ALPHA_COMPOSITE_25);
		g2.fill(path6);

		// 領域7(戻り売り)を描画します。
		final GeneralPath path7 = new GeneralPath();
		path7.moveTo((float) x, (float) y);
		path7.lineTo((float) this.chartArea.getMinX(), (float) (y + h));
		path7.lineTo((float) this.chartArea.getMinX(), (float) (y - h));
		path7.closePath();
		g2.setPaint(sell);
		g2.setComposite(ALPHA_COMPOSITE_25);
		g2.fill(path7);

		// 領域8(売り見送り/突っ込み買い)を描画します。
		final GeneralPath path8 = new GeneralPath();
		path8.moveTo((float) x, (float) y);
		path8.lineTo((float) (x - w), (float) this.chartArea.getMaxY());
		path8.lineTo((float) this.chartArea.getMinX(), (float) this.chartArea.getMaxY());	// XXX - 端まで塗りつぶさない場合はコメント化すること
		path8.lineTo((float) this.chartArea.getMinX(), (float) (y + h));
		path8.closePath();
		g2.setPaint(sell);
		g2.setComposite(ALPHA_COMPOSITE_20);
		g2.fill(path8);

		g2.setComposite(originalComposite);

		// 線を描画します。
		g2.setColor(Color.WHITE);
		g2.draw(new Line2D.Double(x + w, this.chartArea.getMinY(), x - w, this.chartArea.getMaxY()));
		g2.draw(new Line2D.Double(this.chartArea.getMinX(), y + h, this.chartArea.getMaxX(), y - h));
		g2.draw(new Line2D.Double(this.chartArea.getMinX(), y - h, this.chartArea.getMaxX(), y + h));
		g2.draw(new Line2D.Double(x - w, this.chartArea.getMinY(), x + w, this.chartArea.getMaxY()));
	}

	private static final String FORMAT_DATE = StringManager.getString("format.date");

	/**
	 * 逆ウォッチ曲線を描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param price 価格の配列
	 * @param volume 出来高の配列
	 * @param color 線の色
	 */
	private void drawLine(final Graphics2D g2, final Number[] price, final Number[] volume, final Color color) {
		final FontMetrics fm = g2.getFontMetrics();

		for (int i = this.start + 1; i < (this.start + this.period); i++) {
			if (i < 0 || i >= this.dataset.getCount())
				continue;
			if ((i - 1) < 0 || (i - 1) >= this.dataset.getCount())
				continue;
			if (price[i] == null || volume[i] == null)
				continue;
			if (price[i - 1] == null || volume[i - 1] == null)
				continue;

			// 座標を算出します
			final double x1 = this.volumeAxis.valueToJava2D(volume[i - 1].doubleValue(), this.chartArea);
			final double y1 = this.priceAxis.valueToJava2D(price[i - 1].doubleValue(), this.chartArea);
			final double x2 = this.volumeAxis.valueToJava2D(volume[i].doubleValue(), this.chartArea);
			final double y2 = this.priceAxis.valueToJava2D(price[i].doubleValue(), this.chartArea);

			// 描画します
			g2.setColor(color);
			g2.draw(new Line2D.Double(x1, y1, x2, y2));

			if (i == (this.start + 1)) {
				final String text = String.format(FORMAT_DATE, this.dataset.date[i - 1]);
				DrawUtils.drawText(g2, text, (float) (x1 - fm.stringWidth(text) * 0.5), (float) (y1 - 2));
			}

			if ((i + 1) == (this.start + this.period)) {
				final String text = String.format(FORMAT_DATE, this.dataset.date[i]);
				DrawUtils.drawText(g2, text, (float) (x2 - fm.stringWidth(text) * 0.5), (float) (y2 - 2));
			}
		}
	}

}
