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

import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;

import static org.opengion.fukurou.util.HybsConst.CR;				// 6.1.0.0 (2014/12/26) refactoring
import static org.opengion.fukurou.util.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

/**
 * FixLengthData.java は、固定長データを作成するための簡易クラスです。
 *
 * データの項目(String[])を、それぞれの中で最大桁数にあわせて、スペース埋めします。
 * 各項目間に、追加するスペース数は、setAddLength( int[] ) メソッドで、
 * 各項目のタイプ(半角文字、全角混在、数字)の指定は、setType( int[] ) メソッド行います。
 *
 * このクラスは同期処理は保障されていません。
 *
 * @og.rev 5.6.6.0 (2013/07/05) keys の整合性チェックを追加
 *
 * @version  4.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class FixLengthData {

	/** 項目タイプの定義変数：X:半角文字	{@value}	*/
	public static final int X = 0 ;
	/** 項目タイプの定義変数：S:数字(前空白)	{@value}	*/
	public static final int S = 1 ;										// 5.6.6.0 (2013/07/05) 前空白詰めに変更
	/** 項目タイプの定義変数：K:半角全角混在	{@value}	*/
	public static final int K = 2 ;
	/** 項目タイプの定義変数：X9:数字(前ゼロ)	{@value}	*/
	public static final int S0 = 3 ;									// 5.6.6.0 (2013/07/05) 前ゼロ詰めを変更

	/** 項目間空白配列の定義変数：T:タブ区切り	{@value}	*/
	public static final int T  = -1 ;									// 5.6.6.0 (2013/07/05) タブ区切り
	public static final int T2 = -2 ;									// 5.6.6.0 (2013/07/05) タブ区切り
	public static final int T3 = -3 ;									// 5.6.6.0 (2013/07/05) タブ区切り
	public static final int T4 = -4 ;									// 5.6.6.0 (2013/07/05) タブ区切り

	/** 初期 ENCODE 名	{@value}	*/
	public static final String ENCODE = "Windows-31J" ;

	private final int[]	addLen	;		// 各データ間に追加するスペースを設定する。
	private final int[]	type	;		// 各データの固定長形式。 0:半角文字 1:数字(前空白) 2:半角全角混在 3:数字(前ゼロ) 
	private final int	size	;		// データの個数

	private int[]		maxLen	;		// 内部変数。各データの最大長を記憶する。
	private String[]	fill_X	;		// スペースの文字列
	private String[]	fill_S	;		// ゼロの文字列
	private String[]	addSpc	;		// 内部変数。addLen で指定された文字数分の空白を管理します。
	private final List<String[]> list = new ArrayList<>();

	/**
	 * データの項目数を指定して、オブジェクトを構築します。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05) addLen の代わりに、addSpc で管理します。
	 *
	 * @param	len	データの項目数
	 */
	public FixLengthData( final int len ) {
		size	= len ;
		addLen	= new int[size];
		type	= new int[size];
		maxLen	= new int[size];
	}

	/**
	 * 項目間空白配列と各項目のタイプ配列を指定して、オブジェクトを構築します。
	 * どちらも、int型配列なので、順番に注意してください。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05) 新規追加
	 *
	 * @param   inAddLen	データの項目間空白配列
	 * @param   inType		データの各項目のタイプ配列
	 * @see		#setAddLength( int[] )
	 * @see		#setType( int[] )
	 * @throws  IllegalArgumentException 引数が null の場合
	 */
	public FixLengthData( final int[] inAddLen,final int[] inType ) {
		if( inAddLen == null || inType == null ) {
			final String errMsg = "項目間空白配列 または、項目のタイプ配列に、null は、指定できません。";
			throw new IllegalArgumentException( errMsg );
		}

		size	= inAddLen.length ;

		addLen	= new int[size];
		type	= new int[size];
		maxLen	= new int[size];

		setAddLength( inAddLen );
		setType( inType );
	}

	/**
	 * データの項目に対応した、固定時の間に挿入する空白文字数を指定します。
	 * 初期値は、0 です。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05) addLen の代わりに、addSpc で管理します。
	 *
	 * @param   inAddLen	データの項目間空白配列(可変長引数)
	 * @throws  IllegalArgumentException 引数のデータ件数が、コンストラクタで指定した数と異なる場合
	 */
	public void setAddLength( final int... inAddLen ) {
		if( inAddLen != null && inAddLen.length != size ) {		// 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
			final String errMsg = "引数のデータ件数が、コンストラクタで指定した数と異なります。"
								+ "SIZE=[" + size + "] , 引数長=[" + inAddLen.length + "]" ;
			throw new IllegalArgumentException( errMsg );
		}

		System.arraycopy( inAddLen,0,addLen,0,size );
	}

	/**
	 * データの各項目のタイプ(半角文字、数字)を指定します。
	 * X:半角文字の場合は、データを前方に、余った分を後方にスペースを埋めます。
	 * S:数字(前空白)の場合は、データを後方に、余った分を空白を前方に埋めます。
	 * S0:数字(前ゼロ)の場合は、データを後方に、余った分をゼロを前方に埋めます。
	 * K:半角全角混在の場合は、ENCODE(Windows-31J) で文字数を求めるとともに、X:半角文字と同様の処理を行います。
	 * 初期値は、X:半角文字 です。
	 *
	 * @param   inType	データの各項目のタイプ配列(可変長引数)
	 * @see #X
	 * @see #S
	 * @see #S0
	 * @see #K
	 * @throws  IllegalArgumentException 引数のデータ件数が、コンストラクタで指定した数と異なる場合
	 */
	public void setType( final int... inType ) {
		if( inType != null && inType.length != size ) {			// 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
			final String errMsg = "引数のデータ件数が、コンストラクタで指定した数と異なります。"
								+ "SIZE=[" + size + "] , 引数長=[" + inType.length + "]" ;
			throw new IllegalArgumentException( errMsg );
		}

		System.arraycopy( inType,0,type,0,size );
	}

	/**
	 * データの各項目に対応した配列データを設定します。
	 * 配列データを登録しながら、各項目の最大データ長をピックアップしていきます。
	 *
	 * @param	inData	データの各項目の配列(可変長引数)
	 * @throws  IllegalArgumentException 引数のデータ件数が、コンストラクタで指定した数と異なる場合
	 */
	public void addListData( final String... inData ) {
		if( inData != null && inData.length != size ) {			// 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
			final String errMsg = "引数のデータ件数が、コンストラクタで指定した数と異なります。"
								+ "SIZE=[" + size + "] , 引数長=[" + inData.length + "]" ;
			throw new IllegalArgumentException( errMsg );
		}

		// 最大データ長の取得のみ行っておきます。
		try {
			for( int i=0; i<size; i++ ) {
				if( inData[i] != null ) {
					// 6.1.1.0 (2015/01/17) 意味の異なる変数の使いまわしをしているので、修正する。
					final int len = type[i] == K ? inData[i].getBytes( ENCODE ).length : inData[i].length();
					if( maxLen[i] < len ) { maxLen[i] = len; }
				}
			}
		}
		catch( UnsupportedEncodingException ex ) {
			final String errMsg = "データの変換に失敗しました。[" + ENCODE + "]" ;
			throw new RuntimeException( errMsg,ex );
		}
		list.add( inData );
	}

	/**
	 * 指定の行に対する固定文字数に設定された文字列を返します。
	 * 引数の行番号は、addListData(String[])メソッドで登録された順番です。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05) addLen の代わりに、addSpc で管理します。
	 *
	 * @param	line	行番号(addListData で登録した順)
	 *
	 * @return	固定文字数に設定された文字列
	 * @og.rtnNotNull
	 */
	public String getFixData( final int line ) {
		if( fill_X == null ) { makeSpace(); }	// 初期処理

		final String[] data = list.get( line );
		final StringBuilder rtn = new StringBuilder( BUFFER_MIDDLE );
		for( int i=0; i<size; i++ ) {
			final String dt = ( data[i] == null ) ? "" : data[i] ;
			switch( type[i] ) {
				case X: // 文字を出力してから、スペースで埋める。
						rtn.append( dt );
						rtn.append( fill_X[i].substring( dt.length() ) );
						break;
				case S: // 空白で埋めてから、文字を出力する。
						rtn.append( fill_X[i].substring( dt.length() ) );
						rtn.append( dt );
						break;
				case S0: // ゼロで埋めてから、文字を出力する。
						rtn.append( fill_S[i].substring( dt.length() ) );
						rtn.append( dt );
						break;
				case K: // 全角を含む文字を出力してから、スペースで埋める。
						try {
							final int len = dt.getBytes( ENCODE ).length ;
							rtn.append( dt );
							rtn.append( fill_X[i].substring( len ) );
						}
						catch( UnsupportedEncodingException ex ) {
							final String errMsg = "データの変換に失敗しました。[" + ENCODE + "]" ;
							throw new RuntimeException( errMsg,ex );
						}
						break;
				default: // 基本的にありえない
						final String errMsg = "不正な種別が指定されました[" + type[i] + "]" ;
						throw new RuntimeException( errMsg );
				//		break;
			}
			rtn.append( addSpc[i] );		// 5.6.6.0 (2013/07/05) 項目間のスペースを出力
		}
		return rtn.toString();
	}

	/**
	 * データの各項目に対応した配列データを、すべて設定します。
	 * ここでは、配列の配列型データを受け取り、内部的に、addListData( String[] )を
	 * 実行しています。
	 * 簡易的なメソッドです。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05) 新規追加
	 *
	 * @param	inData	データの各項目の配列データの配列
	 * @see		#addListData( String[] )
	 * @throws  IllegalArgumentException 引数のデータ件数が、コンストラクタで指定した数と異なる場合
	 */
	public void addAllListData( final String[][] inData ) {
		for( int i=0; i<inData.length; i++ ) {
			addListData( inData[i] );
		}
	}

	/**
	 * 内部登録済みのすべてのデータを連結して出力します。
	 * 連結時には、改行コードを設定しています。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05) getAllFixData( StringBuilder ) を使用するように内部処理を変更
	 *
	 * @return	固定文字数に設定された文字列
	 * @og.rtnNotNull
	 * @see		#getFixData( int )
	 * @see		#getAllFixData( StringBuilder )
	 */
	public String getAllFixData() {
		return getAllFixData( new StringBuilder( 1000 ) ).toString();
	}

	/**
	 * 内部登録済みのすべてのデータを引数のStringBuilderに連結して返します。
	 * 連結時には、改行コードを設定しています。
	 * return オブジェクトは、この引数と同一のオブジェクトです。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05) 新規追加
	 *
	 * @param	buf 連結に使用する StringBuilder
	 * @return	固定文字数に設定された StringBuilder(入力と同じ)
	 * @og.rtnNotNull
	 * @see		#getFixData( int )
	 * @see		#getAllFixData()
	 */
	public StringBuilder getAllFixData( final StringBuilder buf ) {
		final int len = list.size();
		for( int i=0; i<len; i++ ) {
			buf.append( getFixData( i ) ).append( CR );
		}

		return buf;
	}

	/**
	 * 固定文字列を作成するための種となるスペース文字列とゼロ文字列を作成します。
	 *
	 * @og.rev 5.6.6.0 (2013/07/05) addLen の代わりに、addSpc で管理します。
	 */
	private void makeSpace() {
		fill_X	= new String[size];
		fill_S	= new String[size];
		addSpc	= new String[size];
		char[] ch ;

		int startCnt = 0;				// 先頭からの文字数
		for( int i=0; i<size; i++ ) {
			// addLen に、T(タブ)が指定された場合、addSpc は、4の倍数になるように調整する。
			startCnt += maxLen[i];
			int addCnt = addLen[i] ;
			if( addCnt < 0 ) {					// T,T2,T3,T4 のケース
	//			addSpc[i] = TAB[-addCnt];
				addCnt = (-4 * addCnt) - (startCnt % 4);		// TAB数に合わせたスペースに換算した数
			}
	//		else {
				ch = new char[addCnt];
				Arrays.fill( ch, ' ' );
				addSpc[i] = String.valueOf( ch );
	//		}
			startCnt += addCnt ;

			ch = new char[maxLen[i]];
			switch( type[i] ) {
				case S0:
						Arrays.fill( ch, '0' );
						fill_S[i] = String.valueOf( ch );
						break;
				case X:
				case S:
				case K:
						Arrays.fill( ch, ' ' );
						fill_X[i] = String.valueOf( ch );
						break;
				default: // 基本的にありえない
						final String errMsg = "不正な種別が指定されました[" + type[i] + "]" ;
						throw new RuntimeException( errMsg );
				//		break;
			}
		}
	}

	/**
	 * 内部変数のデータと、最大値のキャッシュをクリアします。
	 *
	 * それ以外の変数(size、addLength、type)は、設定時のまま残っています。
	 *
	 */
	public void clear() {
		list.clear() ;
		maxLen	= new int[size];
		fill_X	= null;		// スペースの文字列
		fill_S	= null;		// ゼロの文字列
		addSpc	= null;		// 項目間空白
	}
}
