/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.hayabusa.io;

import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.chart.renderer.category.CategoryItemRendererState;
import org.jfree.ui.RectangleEdge;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Color;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;

import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.general.DatasetUtilities;
import org.jfree.data.Range;
import org.jfree.util.ShapeUtilities;

import java.awt.geom.AffineTransform;

/**
 * HybsLineRenderer は、org.jfree.chart.renderer.category.LineAndShapeRenderer を
 * 拡張したカスタマイズクラスです。
 * これは、描画に対して、予め制限を設けて、処理速度の向上を図っています。
 *
 * @og.rev 3.8.9.2 (2007/07/28) 新規作成
 *
 * @version  0.9.0  2001/05/05
 * @author   Kazuhiko Hasegawa
 * @since    JDK1.1,
 */
public class HybsLineRenderer extends LineAndShapeRenderer implements HybsDrawItem {
	private static final long serialVersionUID = 519020100801L ;

	private transient ValueMarkOverColors overColors = null;	// 4.0.3.0 (2008/01/07) マーカーラインでShapeを切り替える時の色指定

	private	Color[] shapeColors	 = null;		// 4.0.3.0 (2008/01/07) データ毎にShapeを切り替える時の色指定
	private	double	visibleLimit = Double.NEGATIVE_INFINITY;
	private int		dynamicOCNo  = -1;			// 4.1.1.0 (2008/02/04) 動的なマーカーラインの基準シリーズ番号
	private	String	shapeScale	 = null;		// 4.1.1.0 (2008/02/04) shapeの大きさの倍率
	private boolean isItemLabelLastVisible = false;	// 4.1.2.0 (2008/03/12)
	private final int hsCode = Long.valueOf( System.nanoTime() ).hashCode() ;		// 4.3.1.1 (2008/08/23)

	private Color[]		categoryColor		= null;		// 6.0.2.1 (2014/09/26) categoryカラー配列

	/**
	 * Creates a renderer with both lines and shapes visible by default.
	 */
	public HybsLineRenderer() {
		super(true, true);
	}

	/**
	 * Creates a new renderer with lines and/or shapes visible.
	 *
	 * @param lines  draw lines?
	 * @param shapes  draw shapes?
	 */
	public HybsLineRenderer( final boolean lines, final boolean shapes ) {
		super(lines,shapes);
	}

	/**
	 * データ毎にShapeを切り替える時の色の繰返しパターンを文字列配列で指定します。
	 *
	 * HybsLine でのみ使用可能です。
	 * これは、データそのものが、繰返し性のある場合に、その繰返し性に対応した
	 * 形状のShape を表示させる場合に使用します。
	 * 繰返しShapeの形状は、JFreeChart のシリーズ毎の繰返し標準形状を使用します。
	 * 現在のバージョンでは、１０個までの繰返しに対応可能です。
	 * 繰返し色を、指定した分だけ、順に使用されていきます。
	 *
	 * 指定文字列は、java.awt.Color クラスのstatic フィールド名で指定します。
	 * BLACK , BLUE , CYAN , DARK_GRAY , GRAY , GREEN , LIGHT_GRAY ,
	 * MAGENTA , ORANGE , PINK , RED , WHITE , YELLOW , (PURPLE) が指定できます。
	 * また、#XXXXXX形式の16bitRGB表記 でも指定可能です。
	 * (独自メソッド)
	 *
	 * @og.rev 4.0.3.0 (2008/01/07) 新規追加
	 *
	 * @param	colors	データ毎の色の繰返しパターン(文字列配列)
	 * @see		java.awt.Color#BLACK
	 */
	protected void setShapeColors( final Color[] colors ) {
		shapeColors = colors;
	}

	/**
	 * shapeの大きさを倍率指定で変更します(初期値:null)。
	 *
	 * ラインチャートのShape(各グラフのポイントのマーカー)の大きさは、通常は、
	 * 自動設定されます。
	 * この大きさを、倍率指定で、変更可能です。
	 * 指定は、double 型です。
	 * 初期値は、null は、スケール変更しません(自動設定のままの大きさ)
	 * (独自メソッド)
	 *
	 * @og.rev 4.1.1.0 (2008/02/04) 新規追加
	 *
	 * @param	scale	shapeの大きさの倍率
	 */
	protected void setShapeScale( final String scale ) {
		shapeScale = scale;
	}

	/**
	 * マーカーラインの超過時のShape色管理クラスを設定します。
	 *
	 * 動的なマーカーラインを使用する場合は、引数のシリーズデータが
	 * マーカーラインの最下位閾値に相当します。これは、グラフ化されますが、
	 * Shape は自動的に削除されます。
	 * 逆に、最上位のデータ(シリーズ＝０)のShape は必ず付けます。
	 * (独自メソッド)
	 *
	 * @og.rev 4.1.0.1(2008/01/19) 新規追加
	 * @og.rev 4.1.1.0 (2008/02/04) 動的なオーバーカラー
	 *
	 * @param	vmoc	マーカーラインの超過時のShape色管理クラス
	 * @param	dynamicOverColorNo	動的なマーカーラインの基準シリーズ番号
	 */
	protected void setValueMarkOverColors( final ValueMarkOverColors vmoc,
										final int dynamicOverColorNo ) {
		overColors	= vmoc;
		dynamicOCNo	= dynamicOverColorNo;
	}

	/**
	 * 表示下限値(これ以下のデータは未表示)の値(double)を指定します。
	 *
	 * HybsLine でのみ使用可能です。
	 * この設定値以下のデータは、存在しない扱いとします。
	 * Lineを引くとき、このデータと、存在しているデータ間にラインは引かれません。
	 * 何も指定しない場合は、設定しません。
	 * (独自メソッド)
	 *
	 * @og.rev 4.0.3.0 (2008/01/07) 新規追加
	 *
	 * @param	limit	表示下限値(これ以下のデータは未表示)
	 */
	protected void setVisibleLimit( final double limit ) {
		visibleLimit = limit;
	}

	/**
	 * itemLabelVisible 時に、最後の値のみ表示するかどうか[true:有効/false:無効]を指定します。
	 *
	 * これは、itemLabelVisible 属性に、"last" という設定値を指定した場合は、
	 * 最後のみラベル表示します。
	 * このメソッドでは、true が指定された場合は、"last" 属性が有効になったと
	 * 判断します。
	 * (独自メソッド。HybsDrawItem より継承)
	 *
	 * @og.rev 4.1.2.0 (2008/03/12) 新規追加
	 *
	 * @param	flag	最後の値のみ表示するかどうか[true:有効/false:無効]
	 */
	@Override
	public void setItemLabelLastVisible( final boolean flag ) {
		isItemLabelLastVisible = flag;
	}

	/**
	 * categoryカラー配列を設定します。
	 *
	 * これは、HybsJDBCCategoryDataset クラスで、カテゴリカラーを指定した場合に、
	 * そこから取り出した値をセットすることで、Hybs***Renderer に設定して使います。
	 * Hybs***Renderer 側では、このカラー配列を使用して、getItemPaint(int,int) を
	 * オーバーライドして使います。
	 * (独自メソッド。HybsDrawItem より継承)
	 *
	 * @og.rev 6.0.2.1 (2014/09/26) 新規追加
	 *
	 * @param	categoryColor	categoryカラー配列
	 */
	@Override
	public void setCategoryColor( Color[] categoryColor ) {
		this.categoryColor = categoryColor;
	}

	/**
	 * Returns the paint used to color data items as they are drawn.
	 * <p>
	 * The default implementation passes control to the
	 * <code>lookupSeriesPaint()</code> method. You can override this method
	 * if you require different behaviour.
	 *
	 * @param row  the row (or series) index (zero-based).
	 * @param column  the column (or category) index (zero-based).
	 *
	 * @return カテゴリ別のColorオブジェクト
	 */
	@Override
	public Paint getItemPaint( final int row, final int column ) {
		if( categoryColor == null ) { return super.getItemPaint( row,column ); }
		return categoryColor[column];
	}

	/**
	 * drawItem と同等の機能を持った、高速版メソッドです。
	 *
	 * @og.rev 4.0.3.0 (2008/01/07) shapeColors 属性追加
	 * @og.rev 4.1.1.0 (2008/02/04) 繰返しshapeの開始番号(shapeStartNo)追加
	 * @og.rev 4.1.1.0 (2008/02/04) seriesColors 属性は、色(Paint)のみ切り替えるようにする。
	 * @og.rev 4.1.1.0 (2008/02/04) ラベルブレイク機能の追加(HybsCategoryAxis)
	 * @og.rev 4.1.1.0 (2008/02/04) 動的なマーカーライン
	 * @og.rev 4.1.1.0 (2008/02/22) Stroke を設定するロジックを追加
	 * @og.rev 4.1.2.0 (2008/03/12) ラベルのアンダーライン時にItemLavelを表示しない
	 *
	 * @param g2			Graphics2Dオブジェクト
	 * @param state			CategoryItemRendererStateオブジェクト
	 * @param dataArea		Rectangle2Dオブジェクト
	 * @param plot			CategoryPlotオブジェクト
	 * @param domainAxis	CategoryAxisオブジェクト
	 * @param rangeAxis		ValueAxisオブジェクト
	 * @param dataset		CategoryDatasetオブジェクト
	 * @param serNo			シリアル番号
	 */
	@Override
	public void drawItem2( final Graphics2D g2, final CategoryItemRendererState state,
			final Rectangle2D dataArea, final CategoryPlot plot, final CategoryAxis domainAxis,
			final ValueAxis rangeAxis, final CategoryDataset dataset, final int serNo ) {

		int clmCount = dataset.getColumnCount();
		int rowCount = dataset.getRowCount();
		RectangleEdge domEdge   = plot.getDomainAxisEdge();
		RectangleEdge rangeEdge = plot.getRangeAxisEdge();

		boolean isShape = getBaseShapesVisible() ;

		HybsCategoryAxis hybsAxis = null;
		if( domainAxis instanceof HybsCategoryAxis ) {
			hybsAxis = (HybsCategoryAxis)domainAxis;
			hybsAxis.setItemLabelLastVisible( isItemLabelLastVisible );
		}

		// データ毎にShapeを切り替える時の色の繰返しパターン
		int shpCnt = ( shapeColors == null ) ? 1 : shapeColors.length;

		// Shape の形状を指定できる。任意ではなく、表示順の開始位置の指定
		int shapeNo = 0 ;

		// shapeの大きさの倍率
		AffineTransform transform = null;
		if( shapeScale != null ) {
			double scale = Double.parseDouble( shapeScale );
			transform = AffineTransform.getScaleInstance( scale, scale );
		}

		// 4.1.1.0 (2008/02/22) Stroke を設定するロジックを追加
		Stroke baseStroke = getBaseStroke();
		if( baseStroke != null ) { g2.setStroke( baseStroke ); }

		// トリッキー：row == serNo を処理したいがために、１回余分にループをまわす。
		for( int row=0; row<=rowCount; row++ ) {
			if( row == serNo ) { continue; }	// Mis Add 2007/07/23
			if( row >= rowCount ) {
				if( serNo >= 0 ) {
	 				row = serNo;
					rowCount = -1;		// 終了条件
				}
				else {
					break;
				}
			}

			// 4.1.1.0 (2008/02/22) Stroke を設定するロジックを追加
			Stroke serStroke = getSeriesStroke( row );
			if( serStroke != null ) { g2.setStroke( serStroke ); }

			Paint rowPaint = lookupSeriesPaint( row );
			Shape rowShape = lookupSeriesShape( shapeNo );

			// shapeの大きさの倍率
			if( transform != null ) {
				rowShape = transform.createTransformedShape(rowShape);
			}

			Paint linePaint = rowPaint ;
			// 4.1.1.0 (2008/02/04) 動的なマーカーライン(row==dynamicOCNoのデータで判定)
			boolean shapeFlag = false;
			if( row == dynamicOCNo ) {	// 使わない場合は、-1 なので、マッチしない。
				if( overColors != null ) {
					linePaint = overColors.getDynamicColor();
				}
			}
			else {
				// 動的なマーカーライン使用時(dynamicOCNo >= 0)は、row == 0 で、Shape を使います。
				if( isShape || row == serNo || ( dynamicOCNo >= 0 && row == 0 ) ) {
					shapeFlag = true;
					shapeNo++ ;		// Shape 形状の変更は、使用した場合のみ
				}
			}

			double v0 = 0;
			double x0 = 0;
			double y0 = 0;

			boolean isItemLabelsVisible = isSeriesItemLabelsVisible( row );

			int clmSeq = 0;		// カラムの繰返し制御(Shape色の順番表示用)
			for( int column=0; column<clmCount; column++ ) {
				// nothing is drawn for null...
				Number v1Num = dataset.getValue( row,column );
				if(v1Num == null) { continue; }
				double v1 = v1Num.doubleValue();
				double x1 = domainAxis.getCategoryMiddle( column,clmCount,dataArea,domEdge );
				double y1 = rangeAxis.valueToJava2D( v1,dataArea,rangeEdge );

				// Line の描画
				if( column > 0 && v0 >= visibleLimit && v1 >= visibleLimit ) {
					Line2D line = new Line2D.Double( x0,y0,x1,y1 );
					g2.setPaint( linePaint );
					g2.draw( line );
				}

				// Shape の描画
				if( shapeFlag ) {
					// ラベルブレイク処理
					if( hybsAxis != null && hybsAxis.isLabelBreak( column ) ) {
						clmSeq = 0;		// 初期化
					}

					int adrs = clmSeq%shpCnt;
					clmSeq++ ;
					Paint paint = ( shapeColors == null ) ? rowPaint : shapeColors[adrs];
					// 4.1.1.0 (2008/02/04) 動的なマーカーライン(row==dynamicOCNoのデータで判定)
					if( overColors != null ) {
						if( dynamicOCNo >= 0 ) {
							paint = overColors.getColor( v1,dataset.getValue( dynamicOCNo,column ) );
						}
						else {
							paint = overColors.getColor( v1 );
						}
					}
					g2.setPaint(paint);
					Shape shape = ShapeUtilities.createTranslatedShape( rowShape,x1,y1 );
					g2.fill(shape);
					g2.setPaint(rowPaint);	// 色を戻す。
					// 4.3.1.0 (2008/08/09) add an item entity, if this information is being collected
					EntityCollection entities = state.getEntityCollection();
					if( entities != null ) {
						addItemEntity( entities, dataset, row, column, shape );
					}
				}

				// ItemLabel の描画
				//   山形 0-1-0 nega=fale , x= 0 上中
				//   右坂 0-1-2 nega=true , x=10 下右
				//   谷形 1-0-1 nega=true , x= 0 下中
				//   左坂 2-1-0 nega=fale , x=10 上右
				if( isItemLabelsVisible ) {
					// 4.1.2.0 (2008/03/12) アンダースコアの場合は、表示しない。
					if( hybsAxis != null && hybsAxis.isViewItemLabel( column ) ) {
						double v2 = v0 ;	// 仮設定(最後のcolumnとnull の場合)
						if( column+1 < clmCount ) {
							Number v2Num = dataset.getValue( row,column+1 );
							if( v2Num != null ) {
								v2 = v2Num.doubleValue();
							}
						}
						boolean nega = v1<v2 ;
						double  lblx = ( nega && v0<v1 || !nega && v0>v1 ) ? x1 + 10 : x1 ;

						drawItemLabel( g2,PlotOrientation.VERTICAL,dataset,row,column,lblx,y1,nega );
					}
				}

				v0 = v1;	// Lineを消す処理に、過去の値が必要
				x0 = x1;
				y0 = y1;
			}
		}
	}

	/**
	 * このオブジェクトと指定されたオブジェクトを比較します。
	 *
	 * @og.rev 4.3.1.1 (2008/08/23) 新規追加
	 * @og.rev 5.1.9.0 (2010/08/01) findbug対応
	 *
	 * @param	anObject	比較されるオブジェクト
	 *
	 * @return	指定されたオブジェクトが等しい場合は true、そうでない場合は false
	 */
	@Override
	public boolean equals( final Object anObject ) {
		if( super.equals( anObject ) ) {
			return hsCode == ((HybsLineRenderer)anObject).hsCode;
		}
		return false;
	}

	/**
	 * このオブジェクトのハッシュコードを返します。
	 *
	 * @og.rev 4.3.1.1 (2008/08/23) 新規追加
	 * @og.rev 5.1.9.0 (2010/08/01) findbug対応
	 *
	 * @return	このオブジェクトのハッシュコード値
	 */
	@Override
	public int hashCode() { return hsCode ; }

	/**
	 * 指定されたデータセットからのアイテムをすべて表示するために、要求する値の範囲を返します。
	 *
	 * @param	dataset	カテゴリDataset
	 *
	 * @return	Rangeオブジェクト
	 */
	@Override
	public Range findRangeBounds( final CategoryDataset dataset ) {
		if( dataset instanceof HybsDataset ) {
			return ((HybsDataset)dataset).getRange();
		}
		else {
			return DatasetUtilities.findRangeBounds(dataset);
		}
	}

	/**
	 * シリアライズ用のカスタムシリアライズ書き込みメソッド
	 *
	 * @og.rev 4.1.0.1(2008/01/19) 新規追加
	 * @serialData 一部のオブジェクト(ValueMarkOverColors)は、シリアライズされません。
	 *
	 * @param	strm	ObjectOutputStreamオブジェクト
	 * @throws IOException 入出力エラーが発生した場合
	 */
	private void writeObject( final ObjectOutputStream strm ) throws IOException {
		strm.defaultWriteObject();
	}

	/**
	 * シリアライズ用のカスタムシリアライズ読み込みメソッド
	 *
	 * ここでは、transient 宣言された内部変数の内、初期化が必要なフィールドのみ設定します。
	 *
	 * @og.rev 4.1.0.1(2008/01/19) 新規追加
	 * @serialData 一部のオブジェクト(ValueMarkOverColors)は、読み込まれません。
	 *
	 * @param	strm	ObjectInputStreamオブジェクト
	 * @throws IOException	シリアライズに関する入出力エラーが発生した場合
	 * @throws ClassNotFoundException	クラスを見つけることができなかった場合
	 */
	private void readObject( final ObjectInputStream strm ) throws IOException , ClassNotFoundException {
		strm.defaultReadObject();
	}
}
