/*
 * 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 java.util.List;
import java.util.ArrayList;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import org.jfree.ui.RectangleEdge;
import org.jfree.text.TextBlock;
import org.jfree.chart.axis.AxisState;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryAnchor;

import org.opengion.fukurou.util.StringUtil;						// 6.2.0.0 (2015/02/27)

/**
 * HybsCategoryAxis は、CategoryAxis クラスを継承した、横軸管理クラスです。
 * 横軸ラベルの表示制御を、主に行っています。
 * 横軸表示には、３つの制御機能がカスタマイズされています。
 *
 *  １．"_" ラベルのスキップ(非表示)
 *  ２．cutNo 属性による、ラベル文字位置指定のキーブレイク
 *  ３．skip 属性による、ラベルをスキップする間隔の指定
 *
 * 上記、１，２，３ の順番で優先的に処理されます。
 *
 * @version  0.9.0	2007/06/21
 * @author	 Kazuhiko Hasegawa
 * @since	 JDK1.1,
 */
public class HybsCategoryAxis extends CategoryAxis {
	private static final long serialVersionUID = 519020100801L ;

	private static final TextBlock NULL_LABEL = new TextBlock() ;

	/**
	 * LabelVisible の状態を表す、enum 定義です。
	 *
	 * TRUE(true) , FALSE(false) , UNDER(true) を持っています。
	 *
	 * @og.rev 4.1.2.0 (2008/03/12) 新規追加
	 */
	private enum LabelVisible { TRUE(true) , FALSE(false) , UNDER(true) ;
		private final boolean flag ;

		/**
		 * enumのコンストラクタ
		 *
		 * @param	flag	boolean型の値
		 */
		LabelVisible( final boolean flag ) { this.flag = flag; }

		/**
		 * enum内部の値を、boolean 型に変換します。
		 *
		 * @return	boolean 型の値
		 */
		public boolean booleanValue() { return flag; }
	};

	/** For serialization. */
	private int skip  = 1;				// skip数
	private int count	;				// skip 時の現在位置のカウント

	private transient List<LabelVisible> labelBreak	;
	private int		cutNo			= -1;		// 4.1.1.0 (2008/02/04) ラベルブレイクのsubstring 位置
	private String	breakKey		;			// 4.1.1.0 (2008/02/04) ラベルブレイクの前回キー
	private boolean isLastVisible	;			// 4.1.2.0 (2008/03/12) 6.0.2.5 (2014/10/31) refactoring
	private final int hsCode = Long.valueOf( System.nanoTime() ).hashCode() ;	// 5.1.9.0 (2010/08/01) equals,hashCode

	/**
	 * 引数を指定して作成する コンストラクター
	 *
	 * skip(ラベルの表示間隔) = 1 , cutNo(ラベルブレイクのsubstring 位置) = -1 で初期化します。
	 *
	 * @param	label	ラベル
	 */
	public HybsCategoryAxis( final String label ) {
		this( label,1,-1 );
	}

	/**
	 * 引数を指定して作成する コンストラクター
	 *
	 * @og.rev 4.1.1.0 (2008/02/04) cutNo 新規追加
	 *
	 * @param	label 	ラベル
	 * @param	skip  	ラベルの表示間隔
	 * @param	cutNo 	ラベルブレイクのsubstring 位置
	 */
	protected HybsCategoryAxis( final String label,final int skip,final int cutNo ) {
		super( label );
		this.skip	= skip ;
		this.cutNo	= cutNo ;
	}

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

	/**
	 * 軸を引く場合、使用することができるチックの一時的リストを作成します。
	 *
	 * @og.rev 4.1.1.0 (2008/02/04) labelBreak 新規追加
	 *
	 * @param	g2			Graphics2Dオブジェクト(フォント測定に使用)
	 * @param	state		AxisStateオブジェクト
	 * @param	dataArea	インサイドエリアを示すRectangle2Dオブジェクト
	 * @param	edge		ロケーションを指定するRectangleEdgeオブジェクト
	 *
	 * @return	チックのリスト
	 */
	@Override
	public List<?> refreshTicks( final Graphics2D g2,
							  final AxisState state,
							  final Rectangle2D dataArea,
							  final RectangleEdge edge) {
		count = 0;
		labelBreak = new ArrayList<>();

		return super.refreshTicks( g2, state, dataArea, edge);
	}

	/**
	 * TextBlock オブジェクトを作成します。
	 *
	 * このメソッドでは、３つの拡張機能を実現しています。
	 *  １．"_" ラベルのスキップ(非表示)
	 *  ２．cutNo 属性による、ラベル文字位置指定のキーブレイク
	 *  ３．skip 属性による、ラベルをスキップする間隔の指定
	 *  ４．改行コード(chr(10)、￥n)または、改行コード文字列('￥n'文字列)による、ラベルの段組指定 (6.4.9.2 (2016/08/19))
	 * cutNo が指定された場合は、skip 処理は行われません。また、
	 * その場合のラベルは、cutNoで指定された先頭文字列のみ表示されます。
	 * 文字列が、cutNoで指定された数より小さい場合は、そのまま使用されます。
	 *
	 * @og.rev 4.1.1.0 (2008/02/04) cutNo,labelBreak 追加
	 * @og.rev 4.1.2.0 (2008/03/12) LabelVisible.UNDER 処理を追加
	 * @og.rev 4.3.1.1 (2008/08/23) lbl の null参照はずしの対応
	 * @og.rev 6.4.9.2 (2016/08/19) 横軸ラベルの折り返し対応
	 *
	 * @param	category	カテゴリ名
	 * @param	width		幅
	 * @param	edge		表示範囲を示すRectangleEdgeオブジェクト
	 * @param	g2			Graphics2Dオブジェクト
	 *
	 * @return	TextBlockオブジェクト
	 */
	@SuppressWarnings("rawtypes")
	@Override
	protected TextBlock createLabel( final Comparable category, final float width, final RectangleEdge edge, final Graphics2D g2 ) {
		TextBlock label = null ;
		String    lbl   = null;
		if( category instanceof String ) {					// 4.3.1.1 (2008/08/23) instanceof チェックは、nullチェック不要
			lbl = (String)category;
			if( StringUtil.startsChar( lbl , '_' ) ) {		// 6.2.0.0 (2015/02/27) １文字 String.startsWith
				label = NULL_LABEL;
			}
		}

		if( cutNo > 0 && lbl != null ) {
			if( lbl.length() >= cutNo ) {
				lbl = lbl.substring( 0,cutNo );
			}

			if( ! lbl.equals( breakKey ) ) {
				// 6.4.9.2 (2016/08/19) 横軸ラベルの折り返し対応
				label = multiLabel( lbl, width, edge,  g2 );
				breakKey = lbl ;
			}
		}
		else {
			if( count % skip == 0 ) {
				// 6.4.9.2 (2016/08/19) 横軸ラベルの折り返し対応
				label = multiLabel( category, width, edge,  g2 );
			}
			count++;
		}

		if( label == null ) {
			label = NULL_LABEL;
			labelBreak.add( LabelVisible.FALSE );
		}
		else if( label.equals( NULL_LABEL ) ) {
			labelBreak.add( LabelVisible.UNDER );
		}
		else {
			labelBreak.add( LabelVisible.TRUE );
		}

		return label;
	}

	/**
	 * 複数行の横軸に対応した、TextBlock オブジェクトを作成します。
	 *
	 * ラベルに改行コードを含む場合、ラベルを段組(改行)します。
	 * 改行コードとは、本当の改行コード(ORACLEで言うところの、chr(10)か、
	 * 改行コード文字列('￥n'文字列)を認識します。
	 *
	 * @og.rev 6.4.9.2 (2016/08/19) 横軸ラベルの折り返し対応
	 *
	 * @param	category	カテゴリ名(ラベル)
	 * @param	width		幅
	 * @param	edge		表示範囲を示すRectangleEdgeオブジェクト
	 * @param	g2			Graphics2Dオブジェクト
	 *
	 * @return	TextBlockオブジェクト
	 */
	@SuppressWarnings("rawtypes")
	private TextBlock multiLabel( final Comparable category, final float width, final RectangleEdge edge, final Graphics2D g2 ) {
		final TextBlock label ;
		// splitの関係上、文字列のみ処理します。
		if( category instanceof String ) {
			final String lbl = (String)category;
			final String[] sp = lbl.split( "\\n|\\\\n" );
			label = super.createLabel( sp[0], width, edge,  g2 );		// 一つ目のラベル
			for( int i=1; i<sp.length; i++ ) {							// 改行があれば、ここでループされる。
				final TextBlock lbl2 = super.createLabel( sp[i], width, edge,  g2 );
				label.addLine( lbl2.getLastLine() );					// フォント情報などを引き継ぐ
			}
		}
		else {
			label = super.createLabel( category, width, edge,  g2 );	// 文字列でなければ、そのまま使用する。
		}

		return label;
	}

	/**
	 * ラベルブレイクするかどうかを返します。
	 *
	 * skip または、cutNo によるラベルの間引き処理で、指定のCategoryAxis
	 * に対するカラム番号を指定する事で、判定値を返します。
	 * 処理が、Label の作成済みかどうかに依存する為、その判定を先に行います。
	 *
	 * @og.rev 4.1.1.0 (2008/02/04) 新規追加
	 *
	 * @param column カラム番号
	 *
	 * @return	ラベルブレイクするかどうか(true:する)
	 */
	protected boolean isLabelBreak( final int column ) {
		return labelBreak == null ||
				labelBreak.size() <= column ||
				labelBreak.get( column ).booleanValue() ;
	}

	/**
	 * ITEM ラベル(各データの設定値の説明用の値)を表示するかどうかを返します。
	 *
	 * ラベルの先頭に、アンダースコアがついたラベルは、ラベルの表示と
	 * ItemLabel の表示を抑止します。(false)
	 * それ以外のラベルは、表示する(true) を返します。
	 * 処理が、Label の作成済みかどうかに依存する為、その判定を先に行います。
	 *
	 * @og.rev 4.1.2.0 (2008/03/12) 新規追加
	 *
	 * @param	column	カラム番号
	 *
	 * @return	ITEMラベルを表示するかどうか(true:する)
	 */
	protected boolean isViewItemLabel( final int column ) {
		boolean flag = labelBreak == null ||
						labelBreak.size() <= column ||
						labelBreak.get( column ) != LabelVisible.UNDER ;

		if( flag && isLastVisible && labelBreak.size() -1 != column ) {			// 6.0.2.5 (2014/10/31) refactoring
			flag = false;
		}

		return flag;
	}

	/**
	 * ドメイン(横軸)のカテゴリ単位のライン(縦線)の描画位置を返します。
	 *
	 * この位置は、labelBreak が存在しないか、または、ブレークするときのみ
	 * 値を返します。これにより、ライン(縦線)の位置を、グラフの中心から
	 * ずらす事が可能になります。
	 * また、labelBreak により、ラベルを描画しない場合は、線の位置を、０ に
	 * 設定する事で、画面から見えなくします。
	 *
	 * @param	anchor			CategoryAnchorオブジェクト
	 * @param	category		カテゴリ番号
	 * @param	categoryCount	カテゴリ数
	 * @param	area			範囲を表すRectangle2Dオブジェクト
	 * @param	edge			ロケーションを指定するRectangleEdgeオブジェクト
	 *
	 * @return	ライン(縦線)の描画位置
	 */
	@Override
	public double getCategoryJava2DCoordinate( final CategoryAnchor anchor,
											   final int category,
											   final int categoryCount,
											   final Rectangle2D area,
											   final RectangleEdge edge) {

		final double result ;

		// labelBreak が存在しないか、または、ブレークするときのみ値を返す。
		if( isLabelBreak( category ) ) {
			result = super.getCategoryJava2DCoordinate(
							anchor,category,categoryCount,area,edge
					) ;
		}
		else {
			result = 0;
		}
		return result ;
	}

	/**
	 * この文字列と指定されたオブジェクトを比較します。
	 *
	 * 親クラスで、equals メソッドが実装されているため、警告がでます。
	 *
	 * @og.rev 5.1.8.0 (2010/07/01) findbug対応
	 * @og.rev 5.1.9.0 (2010/08/01) findbug対応
	 *
	 * @param	object	比較するオブジェクト
	 *
	 * @return	Objectが等しい場合は true、そうでない場合は false
	 */
	@Override
	public boolean equals( final Object object ) {
		// 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
		return super.equals( object ) && hsCode == ((HybsCategoryAxis)object).hsCode;
	}

	/**
	 * このオブジェクトのハッシュコードを取得します。
	 *
	 * @og.rev 5.1.8.0 (2010/07/01) findbug対応
	 * @og.rev 5.1.9.0 (2010/08/01) findbug対応
	 *
	 * @return	ハッシュコード
	 */
	@Override
	public int hashCode() { return hsCode ; }
}
