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

import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.db.AbstractTableFilter;
import org.opengion.hayabusa.db.DBTableModel;

import org.opengion.fukurou.util.ErrorMessage;
import org.opengion.fukurou.util.StringUtil;

import java.util.Map;
import java.util.HashMap;

/**
 * TableFilter_UNIQ_NAME は、TableFilter インターフェースを継承した、DBTableModel 処理用の
 * 実装クラスです。
 *
 * ここでは、NAME_IN,NAME_OUT,GROUP_KEY,TYPE より、名前を最短ユニーク化します。
 * 例えば、氏名で、姓と名で、同姓の場合、姓(名)を付けることで、区別することができます。
 *
 * パラメータは、tableFilterタグの keys, vals にそれぞれ記述するか、BODY 部にCSS形式で記述します。
 * 【パラメータ】
 *  {
 *       NAME_IN   : NAME_CLM  ;    名前のオリジナルのカラムを指定します。(必須)
 *       NAME_OUT  : RYAKU_CLM ;    変換後の名前を設定するカラムを指定します。NAME_INと同じでもかまいません。(必須)
 *       GROUP_KEY : CDBUMON   ;    名前をユニークにするグループを指定するカラム名を指定します。(選択)
 *                                  グループはソートされている必要があります。内部的にはキーブレイク処理します。
 *       TYPE      : [1 or 2]  ;    処理の方法を指定します(初期値:1)
 *                                    1:姓と名を分けます。重複分は、姓(名) 形式で、ユニークになるまで、名の文字を増やします。
 *                                    2:姓と名を分けます。1. と異なるのは、最初に見つけた重複分は、姓 のまま残します。
 *  }
 *
 * 姓名の分離は、全角または、半角のスペースで区切ります。また、重複しなければ、(名)は付きません。
 * TYPE="2" の方式は、慣例的に、昔からいる社員は苗字そのままで、後から入社した人にだけ(名)を
 * 付けたい場合に、名前を入社年の古い順にならべることで、実現できます。
 *
 * @og.formSample
 * ●形式：
 *      ① &lt;og:tableFilter classId="UNIQ_NAME" keys="NAME_IN,NAME_OUT" vals="NAME_CLM,RYAKU_CLM" /&gt;
 *
 *      ② &lt;og:tableFilter classId="UNIQ_NAME" &gt;
 *                 {
 *                     NAME_IN  : NAME_CLM  ;
 *                     NAME_OUT : RYAKU_CLM ;
 *                 }
 *         &lt;/og:tableFilter&gt;
 *
 * @og.rev 5.5.0.3(2012/03/12) 新規作成
 * @og.rev 5.6.6.0 (2013/07/05) keys の整合性チェックを追加
 *
 * @version  0.9.0  2000/10/17
 * @author   Kazuhiko Hasegawa
 * @since    JDK1.6,
 */
public class TableFilter_UNIQ_NAME extends AbstractTableFilter {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "6.4.3.4 (2016/03/11)" ;

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.4.1.1 (2016/01/16) keysMap を、サブクラスから設定させるように変更。
	 */
	public TableFilter_UNIQ_NAME() {
		super();
		initSet( "NAME_IN"	, "名前のオリジナルのカラムを指定(必須)"					);
		initSet( "NAME_OUT"	, "変換後の名前を設定するカラムを指定(必須)"				);
		initSet( "GROUP_KEY", "名前をユニークにするグループを指定するカラム名を指定"	);
		initSet( "TYPE"		, "処理方法を指定(初期値:1) [1 or 2]"						);
	}

//	/**
//	 * keys の整合性チェックを行うための初期設定を行います。
//	 *
//	 * @og.rev 5.6.6.1 (2013/07/12) keys の整合性チェック対応
//	 * @og.rev 6.4.1.1 (2016/01/16) keysMap を、サブクラスから設定させるため、廃止。
//	 *
//	 * @param	keysMap keys の整合性チェックを行うための Map
//	 */
//	@Override
//	protected void init( final Map<String,String> keysMap ) {
//		keysMap.put( "NAME_IN"	, "名前のオリジナルのカラムを指定(必須)"		);
//		keysMap.put( "NAME_OUT"	, "変換後の名前を設定するカラムを指定(必須)"	);
//		keysMap.put( "GROUP_KEY", "名前をユニークにするグループを指定するカラム名を指定"	);
//		keysMap.put( "TYPE"		, "処理方法を指定(初期値:1) [1 or 2]"	);
//	}

	/**
	 * DBTableModel処理を実行します。
	 *
	 * @og.rev 5.5.2.6 (2012/05/25) protected変数を、private化したため、getterメソッドで取得するように変更
	 * @og.rev 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
	 *
	 * @return 処理結果のDBTableModel
	 */
	public DBTableModel execute() {
		final DBTableModel table = getDBTableModel();		// 5.5.2.6 (2012/05/25) インターフェースにgetterメソッド追加

		final String nameIn   = getValue( "NAME_IN" );
		final String nameOut  = getValue( "NAME_OUT" );
//		final String groupKey = getValue( "GROUP_KEY" );

//		final int type = StringUtil.nval( getValue( "TYPE" ), 1 );	// NULL　の場合、初期値:1

		final int inClmNo  = table.getColumnNo( nameIn,false );		// 存在しない場合は、-1 を返す。
		final int outClmNo = table.getColumnNo( nameOut,false );
//		final int grpClmNo = table.getColumnNo( groupKey,false );

		// 必須チェック
		if( inClmNo < 0 || outClmNo <0 ) {
			final String errMsg = "TableFilter_UNIQ_NAME では、NAME_IN、NAME_OUT　属性は必須です。"
						+ " NAME_IN =" + nameIn
						+ " NAME_OUT=" + nameOut ;
			throw new HybsSystemException( errMsg );
		}

		// 名前をユニーク化するためのマップ。キーがユニーク化する名前。値は、行番号
		final Map<String,Integer> nameMap = new HashMap<>() ;

		String[] data  = null;
		final int rowCnt = table.getRowCount();
		String preKey = null;
		int row = 0;

		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
		final int type = StringUtil.nval( getValue( "TYPE" ), 1 );	// NULL　の場合、初期値:1
		final String groupKey = getValue( "GROUP_KEY" );
		final int grpClmNo = table.getColumnNo( groupKey,false );
		try {
			for( row=0; row<rowCnt; row++ ) {
				data  = table.getValues( row );
				final String orgName = data[inClmNo];		// オリジナルの名称

				if( grpClmNo >= 0 && preKey == null ) {
					preKey = data[grpClmNo];
					if( preKey == null ) { preKey = ""; }
				}

				if( orgName != null && !orgName.isEmpty() ) {
					final String[] seimei = makeSeiMei( orgName );
					final String sei = seimei[0];
					final String mei = seimei[1];

					if( nameMap.containsKey( sei ) ) {	// 存在する場合。つまり重複
						if( type == 1 ) {	// 重複時に最初の分も(名)を付ける。
							final Integer oldInt = nameMap.get( sei );
							if( oldInt != null ) {	// null の場合は、先に重複処理済み
								// オリジナルの姓名を取得
								final String oldName = table.getValue( oldInt.intValue(),inClmNo );
								final String[] oldSeimei = makeSeiMei( oldName );

								final String key = makeKey( nameMap , oldSeimei[0] , oldSeimei[1] );
								nameMap.put( key, oldInt );		// 変更後のキーと値
								nameMap.put( sei, null );		// 比較用に元のキーは残すが値は残さない。
							}
						}

						final String key = makeKey( nameMap , sei , mei );
						nameMap.put( key, Integer.valueOf( row ) );
					}
					else {
						nameMap.put( sei, Integer.valueOf( row ) );
					}
				}

				// キーブレイクのチェック
				if( grpClmNo >= 0 && !preKey.equals( data[grpClmNo] ) ) {
					preKey = data[grpClmNo];
					if( preKey == null ) { preKey = ""; }

					// 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
					nameMap.forEach( (key,orow) -> {
						if( orow != null ) { table.setValueAt( key , orow , outClmNo ); }
					} );
//					for( final Map.Entry<String,Integer> nameEnt : nameMap.entrySet() ){
//						final Integer orgInt = nameEnt.getValue();
//						if( orgInt != null ) {
//							final int outrow = orgInt.intValue();
//							table.setValueAt( nameEnt.getKey() , outrow , outClmNo );
//						}
//					}
					nameMap.clear();
				}
			}
			// 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
			nameMap.forEach( (key,orow) -> {
				if( orow != null ) { table.setValueAt( key , orow , outClmNo ); }
			} );
//			for( final Map.Entry<String,Integer> nameEnt : nameMap.entrySet() ){
//				final Integer orgInt = nameEnt.getValue();
//				if( orgInt != null ) {
//					final int outrow = orgInt.intValue();
//					table.setValueAt( nameEnt.getKey() , outrow , outClmNo );
//				}
//			}
		}
		catch( RuntimeException ex ) {
			final ErrorMessage errMessage = makeErrorMessage( "TableFilter_UNIQ_NAME Error",ErrorMessage.NG );
			errMessage.addMessage( row+1,ErrorMessage.NG,ex.getMessage() );
			errMessage.addMessage( row+1,ErrorMessage.NG,StringUtil.array2csv( data ) );
			errMessage.addMessage( row+1,ErrorMessage.NG,"NAME_IN=[" + nameIn + "]" );
			errMessage.addMessage( row+1,ErrorMessage.NG,"NAME_OUT=[" + nameOut + "]" );
			errMessage.addMessage( row+1,ErrorMessage.NG,"GROUP_KEY=[" + groupKey + "]" );
			errMessage.addMessage( row+1,ErrorMessage.NG,"TYPE=[" + type + "]" );
		}
		return table;
	}

	/**
	 * オリジナルの姓名から、姓と名を分離します。
	 *
	 * @param	orgName	オリジナルのフルネーム
	 *
	 * @return 姓と名の分割結果
	 */
	private String[] makeSeiMei( final String orgName ) {
		final String[] seimei = new String[2];

		int adrs = orgName.indexOf( ' ' );
		if( adrs < 0 ) { adrs = orgName.indexOf( '　' ); }
		if( adrs < 0 ) {
			seimei[0] = orgName.trim();
			seimei[1] = "";
		}
		else {
			seimei[0] = orgName.substring( 0,adrs ).trim();
			seimei[1] = orgName.substring( adrs+1 ).trim();
		}

		return seimei ;
	}

	/**
	 * マップに存在しないキーを作成します。マップへの登録は、行いません。
	 *
	 * @param	nameMap	過去に登録されている名前キーのマップ
	 * @param	sei		オリジナルの姓
	 * @param	mei		オリジナルの名
	 *
	 * @return	新しく作成されたキー
	 */
	private String makeKey( final Map<String,Integer> nameMap , final String sei , final String mei ) {
		String key = null;

		boolean flag = true;	// 未処理フラグ
		for( int i=1; i<=mei.length(); i++ ) {
			key = sei + "(" + mei.substring(0,i) + ")" ;
			if( ! nameMap.containsKey( key ) ) {	// 存在しない
				flag = false;
				break;
			}
		}
		if( flag ) {
			for( int i=1; i<10; i++ ) {
				key = sei + mei + "("  + i  + ")" ;
				if( ! nameMap.containsKey( key ) ) {	// 存在しない
					break;
				}
			}
		}

		return key ;
	}
}
