/*
 * 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.axis;

import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.util.Calendar;
import java.util.Date;

import jp.sf.orangesignal.chart.ChartColor;
import jp.sf.orangesignal.chart.ui.DatasetType;
import jp.sf.orangesignal.chart.util.DrawUtils;
import jp.sf.orangesignal.chart.util.StringManager;

/**
 * 日付の軸情報を提供します。
 * 
 * @author 杉澤 浩二
 */
public class DateAxis extends Axis {

	/**
	 * 描画幅の下限値です。
	 */
	public static int MINIMUM_DRAW_WIDTH = 100;

	/**
	 * 足単位を保持します。
	 */
	private DatasetType datasetType;

	/**
	 * 期間を保持します。
	 */
	private int period;

	/**
	 * 目盛り単位を保持します。
	 */
	private DateTickUnit tickUnit = DateTickUnit.ONE_MONTH;

	/**
	 * 目盛りをチャート領域に描画するかどうかを保持します。
	 */
	private boolean inside = false;

	/**
	 * デフォルトコンストラクタです。
	 */
	public DateAxis() {
		setOrientation(Orientation.HORIZONTAL);
	}

	/**
	 * 事前処理を行います。
	 * 
	 * @param datasetType 足単位
	 * @param period 期間
	 */
	public void prepare(final DatasetType datasetType, final int period) {
		this.datasetType = datasetType;
		this.period = period;
	}

	/**
	 * 目盛り単位を設定します。
	 * このメソッドは全体チャートでのみ使用します。
	 * それ以外のチャートでは <code>refreshTicks</code> メソッドにより適切な目盛り単位が自動的に選択されます。
	 * 
	 * @param tickUnit 目盛り単位
	 */
	public void setTickUnit(final DateTickUnit tickUnit) { this.tickUnit = tickUnit; }

	/**
	 * 目盛りをチャート領域に描画するかどうかを設定します。
	 * 
	 * @param inside 目盛りをチャート領域に描画するかどうか
	 */
	public void setInside(final boolean inside) { this.inside = inside; }

	// ---------------------------------------- 目盛り計算

	@Override
	public void refreshTicks() {
		final int size = this.period / getTickCount();

		if (this.datasetType == DatasetType.DAILY) {
			if (size > 200)
				this.tickUnit = DateTickUnit.ONE_YEAR;
			else if (size > 100)
				this.tickUnit = DateTickUnit.SIX_MONTH;
			else if (size > 50)
				this.tickUnit = DateTickUnit.THREE_MONTH;
			else
				this.tickUnit = DateTickUnit.ONE_MONTH;
		} else if (this.datasetType == DatasetType.WEEKLY) {
			if (size > 40)
				this.tickUnit = DateTickUnit.ONE_YEAR;
			else if (size > 20)
				this.tickUnit = DateTickUnit.SIX_MONTH;
			else if (size > 10)
				this.tickUnit = DateTickUnit.THREE_MONTH;
			else
				this.tickUnit = DateTickUnit.ONE_MONTH;
		} else if (this.datasetType == DatasetType.MONTHLY) {
			if (size > 10)
				this.tickUnit = DateTickUnit.ONE_YEAR;
			else if (size > 5)
				this.tickUnit = DateTickUnit.SIX_MONTH;
			else if (size > 2.5)
				this.tickUnit = DateTickUnit.THREE_MONTH;
			else
				this.tickUnit = DateTickUnit.ONE_MONTH;
		}
	}

	@Override
	public void refreshTicks(final Graphics2D g2, final Rectangle2D area) {
		final int tickCount = (int) Math.ceil(Math.max(area.getWidth(), MINIMUM_DRAW_WIDTH) / (g2.getFontMetrics(FONT).stringWidth(String.format(StringManager.getString("format.date"), new Date())) * getGap()));
		setTickCount(tickCount);
		this.refreshTicks();
	}

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

	/**
	 * 目盛りやグリッド線を描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 * @param periodWidth 1期間の幅(ピクセル)
	 * @param items 日付の配列
	 * @param start 開始位置
	 * @param period 期間
	 */
	public void draw(final Graphics2D g2, final Rectangle2D area, final double periodWidth, final Date[] items, final int start, final int period) {
		draw(g2, area, new Rectangle2D[]{ area }, periodWidth, items, start, period);
	}

	/**
	 * 目盛りやグリッド線を描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 親描画領域
	 * @param subareas 子描画領域の配列
	 * @param periodWidth 1期間の幅(ピクセル)
	 * @param items 日付の配列
	 * @param start 開始位置
	 * @param period 期間
	 */
	public void draw(final Graphics2D g2, final Rectangle2D area, final Rectangle2D[] subareas, final double periodWidth, final Date[] items, final int start, final int period) {
		g2.setFont(FONT);
		final double top = area.getMaxY() + getTickMarkOutsideLength();
		final Rectangle2D chart = subareas[0];

		if (items != null) {
			final FontMetrics fm = g2.getFontMetrics();
			final Calendar c = Calendar.getInstance();
			int previousMonth = 0;

			for (int i = -1; i < period; i++) {
				final int n = start + i;
				if (n < 0 || n >= items.length || items[n] == null) {
					continue;
				}

				// 新しい月の場合のみ処理を実行します。
				c.setTime(items[n]);
				final int month = c.get(Calendar.MONTH) + 1;
				if (previousMonth != month) {
					if (i != -1) {
						// 1ヶ月単位
						if (this.tickUnit == DateTickUnit.ONE_MONTH && ((month >= 1 && month <= 12) || n == 0)) {
							final double x = chart.getMinX() + i * periodWidth + periodWidth * 0.5;
							draw(g2, fm, subareas, x, top, items[n]);
						// 3ヶ月単位
						} else if (this.tickUnit == DateTickUnit.THREE_MONTH && (month == 1 || month == 4 || month == 7 || month == 10 || n == 0)) {
							final double x = chart.getMinX() + i * periodWidth + periodWidth * 0.5;
							draw(g2, fm, subareas, x, top, items[n]);
						// 6ヶ月単位
						} else if (this.tickUnit == DateTickUnit.SIX_MONTH && (month == 1 || month == 7 || n == 0)) {
							final double x = chart.getMinX() + i * periodWidth + periodWidth * 0.5;
							draw(g2, fm, subareas, x, top, items[n]);
						// 12ヶ月単位
						} else if (this.tickUnit == DateTickUnit.ONE_YEAR && (month == 1 || n == 0)) {
							double x = chart.getMinX() + i * periodWidth;
							if (!this.inside)
								x = x + periodWidth * 0.5;
							draw(g2, fm, subareas, x, top, items[n]);
						}
					}
					previousMonth = month;
				}
			}
		}

		// 線
		if (!this.inside) {
			drawAxisLine(g2, chart.getMinX(), top, chart.getMaxX(), top);
		}
	}

	/**
	 * グリッド線、目盛り、目盛り文字列を描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param fm フォントに関する描画情報
	 * @param areas 描画領域の配列
	 * @param x X 座標
	 * @param top 上辺の座標
	 * @param date 日付
	 */
	private void draw(final Graphics2D g2, final FontMetrics fm, final Rectangle2D[] areas, final double x, final double top, final Date date) {
		// グリッド線を描画します。
		for (final Rectangle2D rect : areas) {
			if (rect != null) {
				drawGlidline(g2, x, rect.getMinY(), x, rect.getMaxY());
			}
		}

		// 目盛りラベル
		final String label = this.tickUnit.valueToString(date);

		if (this.inside) {
			DrawUtils.drawText(g2, label, (float) x, (float) areas[areas.length - 1].getMaxY());
		} else {
			// 目盛り
			drawTickMark(g2, x, top, x, top + getTickMarkInsideLength());
			DrawUtils.drawText(g2, label, (float) (x - fm.stringWidth(label) * 0.5), (float) top + getTickMarkInsideLength() + fm.getAscent() + 1);
		}
	}

	/**
	 * トレースを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 * @param x X 座標
	 * @param margin 余白
	 * @param date 日付
	 */
	public void drawAxisTrace(final Graphics2D g2, final Rectangle2D area, final double x, final int margin, final Date date) {
		// トレース線を描画します。
		drawTraceLine(g2, x, area.getMinY(), x, area.getMaxY());

		if (date == null) {
			return;
		}

		// 各座標を計算します。
		g2.setFont(FONT);
		final FontMetrics fm = g2.getFontMetrics();
		final String text = String.format(StringManager.getString("format.date"), date);

		final double top = area.getMaxY() + getTickMarkOutsideLength();
		final int width = fm.stringWidth(text);
		final int height = fm.getHeight();

		final double x1 = x - width * 0.5;
		final double x2 = x + width * 0.5;
		final double y1 = top + getTickMarkInsideLength() + 1;
		final double y2 = top + getTickMarkInsideLength() + g2.getFontMetrics().getHeight() + 1;

		// マーカー領域を構築します。
		final GeneralPath path = new GeneralPath();
		path.moveTo((float) (x1 - margin), (float) y2);	// 左下
		path.lineTo((float) (x1 - margin), (float) y1);	// 左上
		path.lineTo((float) (x1 + (x2 - x1) * 0.5 - height * 0.5), (float) y1);	// 左付根
		path.lineTo((float) (x1 + (x2 - x1) * 0.5), (float) (top - margin));	// 頂点
		path.lineTo((float) (x1 + (x2 - x1) * 0.5 + height * 0.5), (float) y1);	// 右付根
		path.lineTo((float) (x2 + margin), (float) y1);
		path.lineTo((float) (x2 + margin), (float) y2);
		path.closePath();

		// マーカーを描画します。
		g2.setPaint(new GradientPaint((float) x1, (float) top, Color.WHITE, (float) x1, (float) y2, ChartColor.VERY_LIGHT_GRAY));
		g2.fill(path);
		g2.setColor(Color.BLUE);
		g2.draw(path);

		// マーカーラベルを描画します。
		final double y = top + getTickMarkInsideLength() + fm.getAscent() + 1;
		DrawUtils.drawText(g2, text, (float) x1, (float) y);
	}

}
