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

import java.util.concurrent.ConcurrentMap;							// 6.4.3.3 (2016/03/04)
import java.util.concurrent.ConcurrentHashMap;						// 6.4.3.1 (2016/02/12) refactoring
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;

import org.opengion.hayabusa.common.HybsSystemException;			// 6.4.3.3 (2016/03/04)
import org.opengion.fukurou.util.StringUtil ;
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

/**
 * String[] 型キーにカラム列の連想記憶を用いた、クロス集計データを管理するクラスです。
 *
 * クロス集計では、カラム列が、データとして与えられる為、このクラス内部で、
 * 一旦カラム列の連想記憶(Map)データを作成し、実際の行データ登録時にデータを
 * 設定しています。
 * 取り出すときは、一気に取り出すことを考慮して、配列(ArrayList)データに
 * 共有しているオブジェクトを取り出します。
 *
 * この実装は同期化されません。
 *
 * @og.rev 3.5.4.0 (2003/11/25) 新規作成
 * @og.group 画面表示
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class CrossMap {
	/** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
	private final ConcurrentMap<String,String[]> rowMap = new ConcurrentHashMap<>() ;
	/** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
	private final ConcurrentMap<String,Integer>  clmMap = new ConcurrentHashMap<>() ;
	private final List<String[]> list = new ArrayList<>();
	private final int headCount;
	private final int sumCount;
	private final int totalCols;

	/**
	 * カラム部(クロス集計部)を与えて新しく作成するコンストラクター
	 *
	 * クロス集計を行うカラム部のみセットします。
	 * 行のクロス集計部のヘッダーキーは、引数の配列の順番で、設定されます。
	 * この行のヘッダー部となるデータは、addData 時にセットします。
	 *
	 * @param	clmData		クロス集計部のカラム名配列
	 * @param	headCount 	HEADカラムの数
	 * @param	sumCount  	合計カラムの数
	 */
	public CrossMap( final String[] clmData, final int headCount, final int sumCount ) {
		if( headCount <= 0 ) {
			final String errMsg = "headCount は、ROWカラムを含むため、最低１以上必要です。";
			throw new IllegalArgumentException( errMsg );
		}

		this.headCount = headCount;
		this.sumCount  = sumCount;
		final int clmNum = clmData.length;
		totalCols = headCount + clmNum * sumCount;
		for( int i=0; i<clmNum; i++ ) {
			clmMap.put( clmData[i],Integer.valueOf( i ) );
		}
	}

	/**
	 * クロス集計の元となる検索結果の行データを登録します。
	 *
	 * クロス集計を行うカラム部のみ,セットします。
	 * 行のヘッダー部となるデータは、rowKeys と headCount で指定します。
	 * 行のクロス集計部のヘッダーキーは、clmKey で指定し、内部で、列カラムとして
	 * 割り当てられます。割り当て順(カラム順)は、コンストラクタでの clmData の
	 * 配列の順番です。
	 *
	 * @og.rev 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
	 * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限のチェック追加
	 *
	 * @param	rowKeys	行データの配列(可変長引数)( 0～headCount の値が行のキーとなります。)
	 */
	public void add( final String... rowKeys ) {
		if( rowKeys.length < headCount + 1 + sumCount ) {
			final String errMsg = "指定の rowKeys の個数が不正です。 rowKeys には、clmKey と data が必要です。"
						+ " rowKeys=" + StringUtil.array2csv( rowKeys ) ;	// 5.1.8.0 (2010/07/01) errMsg 修正
			throw new ArrayIndexOutOfBoundsException( errMsg );
		}

		// 3.5.6.6 (2004/08/23) 修正
//		final String clmKey = rowKeys[headCount];		// カラム列のクロス集計部のキー(ヘッダー)
		String[] data = new String[sumCount];			// クロス集計表の値
		for( int i=0; i<sumCount; i++ ) {
			data[i] = rowKeys[headCount+1+i];
		}

		final String rowKey ;
		// 3.5.6.6 (2004/08/23) 修正
		if( headCount == 1 ) { rowKey = rowKeys[0]; }
		else {
			final StringBuilder rKey = new StringBuilder( BUFFER_MIDDLE );
			for( int i=0; i<headCount; i++ ) {
				rKey.append( rowKeys[i] ).append( '_' );			// 6.0.2.5 (2014/10/31) char を append する。
			}
			rowKey = rKey.toString();
		}

		// 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限
		if( rowKey == null ) {
			final String errMsg = "指定の rowKey が、null です。";
			throw new HybsSystemException( errMsg );
		}

		String[] clmData = rowMap.get( rowKey );
		if( clmData == null ) {
			// 行データ+クロス行データ
			clmData = new String[totalCols];
			Arrays.fill( clmData,"" );
			// 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
			System.arraycopy( rowKeys,0,clmData,0,headCount );		// 6.3.6.0 (2015/08/16)
			list.add( clmData );	// 生成順にArrayList にセーブします。
		}

		final String clmKey = rowKeys[headCount];		// カラム列のクロス集計部のキー(ヘッダー)	6.9.7.0 (2018/05/14) PMD before a possible exit point.
		for( int i=0; i<sumCount; i++ ) {
			final int no = headCount + clmMap.get( clmKey ).intValue()*sumCount+i;	// 列番号
			clmData[no] = data[i];
		}
		rowMap.put( rowKey,clmData );	// ArrayList と同じオブジェクトを検索用のMapにもセットする。
	}

	/**
	 * クロス集計結果の指定行の列データを返します。
	 *
	 * @param	row 指定の行番号( 0 .. getSize()-1 )
	 *
	 * @return	列データ配列
	 */
	public String[] get( final int row ) {
		return list.get( row );
	}

	/**
	 * クロス集計結果の行数を返します。
	 *
	 * @return   行数を返します。
	 */
	public int getSize() {
		return list.size();
	}
}
