/*********************************************************************
*  All Rights reserved,Copyright (c) K-Opticom
**********************************************************************
*＜プログラム内容＞
*	システム名			：eo顧客基幹システム
*	モジュール名		：JKKBatOneTimeLogWriter
*	ソースファイル名	：JKKBatOneTimeLogWriter.java
*	作成者				：富士通　
*	作成日				：2015年07月03日
*＜機能概要＞
*　ワンタイムログ出力部品です。
*＜修正履歴＞
*	バージョン	修正日		修正者		修正内容
*	v15.00.00	2015/07/03   FJ)藤田	新規作成【OM-2015-0001775】
*	v15.00.01	2015/08/10   FJ)藤田	【OM-2015-0001775】
*	v15.00.02	2015/08/12   FJ)藤田	【OM-2015-0001775】
*	v15.01.00	2015/08/25   FJ)藤田	【OM-2015-0002122】出力制限数を超えた場合にシスログメッセージ出力を行う
*********************************************************************/
package eo.business.common;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import eo.common.constant.JPCBatchMessageConstant;
import eo.framework.file.JBSbatDefFileUtil;
import eo.framework.file.JBSbatInputFileUtil;
import eo.framework.item.JBSbatCommonItem;
import eo.framework.item.JBSbatOutputItem;
import eo.framework.item.JBSbatServiceInterfaceMap;
import eo.framework.log.JBSbatLogUtil;
import eo.framework.util.JBSbatAplConst;

public class JKKBatOneTimeLogWriter
{
	/** アクセスフラグキー：valuesマップのデータが参照されたことを保存するキー */
	static String AccessedFlgKey = "__PFLG_KEY";

	/** ログ出力日時 */
	static String FirstLogTimeKey = "FIRST_LOG_DTM";

	/** ログファイル名 */
	static String LogFileNm = "LOG_FILE_NM";
	
	/** 出力項目：実行開始日時のフォーマット */
	static String RuntimeStartDtmFmt = "yyyy/MM/dd HH:mm:ss";
	
	/** valuesマップのデータが参照されたことを保存する値 */
	static String AccessedFlgValue = "1";
	
	/** 複数キーを結合してキー文字列を作成する際のセパレータ文字列 */
	static private String KEYS_SEP = "\t";

	/** ファイル出力の制限（デフォルト値） */
	static private int DefaultRecordOutLimitCount = 10000;
	
	/** ユニークレコードマップ */
	private Map<String, Map<String, String>> uniqueRecMap = null;
	
	/** ユニークレコードマップキー登録順配列 */
	private ArrayList<String> uniqueKeySortArray;

	/** 業務共通電文 */
	JBSbatCommonItem commonItem;
	
	/** Key項目名（カラム名） */
	private String[] KeyNames = {};

	/** 出力対象項目名（カラム名） */
	private String[] outColumnList = {};

	/** 定義ファイルID */
	private String defFileId;
	
	/** KeyValueファイル格納ディレクトリパス */
	private String keyValFileDir;
	
	/** KeyValueファイル格納ファイル名 */
	private String keyValFileName;
	
	/** 実行時例外無視フラグ(trueの場合に実行時例外を発生させない) */
	private boolean runtimeExceptionIgnoreFlg;
	
	/** 実行開始日時 */
	private String runtimeStartDtm;
	
	/** 対象ログファイル名 */
	private String targetLogFileName;
	
	/** 実行可否フラグ */
	private boolean execAllowFlg;
	
	/**
	 * コンストラクタ
	 */
	public JKKBatOneTimeLogWriter()
	{
		runtimeExceptionIgnoreFlg = false;
		execAllowFlg = false;
	}
	
	/**
	 * 初期化処理
	 * 
	 * @param commonItem 業務共通電文インスタンス
	 * @param fileId 定義ファイルID
	 * @param directory 入力ファイル保管ディレクトリ
	 * @param fileName 入力ファイル名
	 * @param keyNames キー項目名リスト
	 * @throws Exception 例外
	 */
	public void init(JBSbatCommonItem commonItem, String fileId, String directory, String fileName, String[] keyNames)
		throws Exception
	{
		this.commonItem = commonItem;

		// ユニークレコードマップの初期化
		uniqueRecMap = new HashMap<String, Map<String, String>>();
		uniqueKeySortArray = new ArrayList<String>();
		
		// 実行許可のチェック
		checkExecAllow(commonItem.getJobid());
		if (!execAllowFlg)
		{
			return;
		}
		
		if (directory == null || "".equals(directory))
		{
			execAllowFlg = false;
			commonItem.getLogPrint().printDebugLog(" - 出力ディレクトリが指定されていません");
			commonItem.getLogPrint().printDebugLog(" - 実行可否フラグをfalseにします");
			return;
		}

		if (fileName == null || "".equals(fileName))
		{
			execAllowFlg = false;
			commonItem.getLogPrint().printDebugLog(" - 出力ファイル名が指定されていません");
			commonItem.getLogPrint().printDebugLog(" - 実行可否フラグをfalseにします");
			return;
		}
		
		// 実行ログ
		commonItem.getLogPrint().printDebugLog(" - 初期化処理");
		
		// 実行開始日時(システム日時)のセット
		SimpleDateFormat dateFmt = new SimpleDateFormat(RuntimeStartDtmFmt);
		Date date = new Date(System.currentTimeMillis());
		runtimeStartDtm = dateFmt.format(date);
		
		// ログファイル名のセット
		targetLogFileName = JBSbatLogUtil.APP_FILE_NAME;
		if (targetLogFileName != null)
		{
			String[] sp = targetLogFileName.split("/");
			if (sp.length > 0)
			{
				targetLogFileName = sp[sp.length - 1];
			}
		}
		
		this.defFileId = fileId;
		this.keyValFileDir = directory;
		this.keyValFileName = fileName;
		this.KeyNames = keyNames;
		
		// 定義ファイル名
		String fileDefName = defFileId + ".def";
		// 入力ファイル(fullPath)
		String keyValFilePath = keyValFileDir + keyValFileName;
		
		// 入力ファイルユーティリティの生成
		JBSbatInputFileUtil inFileObj = new JBSbatInputFileUtil(keyValFilePath);
		
		//定義ファイル名(fullPath)を取得する。
		String fileDefNameFull = JBSbatAplConst.getAplConstValue("OTD") + fileDefName;
		// 定義ファイルオブジェクトを生成する
		JBSbatDefFileUtil fileDef = new JBSbatDefFileUtil(fileDefNameFull, inFileObj);
		
		// 出力対象項目名取得
		ArrayList columnDefList = fileDef.getOutDef();
		ArrayList<String> headerColumns = new ArrayList<String>();
		for (int i=0; i < columnDefList.size(); i++)
		{
			// "ERR_PTN_CD,,,String,3,"などから、カラム名を切り出し⇒sp[0]
			String[] sp = ((String)columnDefList.get(i)).split(",");
			if (sp.length > 0)
			{
				if (AccessedFlgKey.equals(sp[0]))
				{
					throw new Exception(fileDefName + "にこの項目名は指定できません [" + AccessedFlgKey + "]");
				}
				headerColumns.add(sp[0]);
			}
		}
		outColumnList = (String[])headerColumns.toArray(new String[headerColumns.size()]);
		
		// ファイルの読み込みを行う
		ArrayList<ArrayList<String>> records = new ArrayList<ArrayList<String>>();
		File inF = new File(keyValFilePath);
		if (inF.exists())
		{
			JBSbatZMFileReaderUtil inFile = null;
			try
			{
				inFile = new JBSbatZMFileReaderUtil(keyValFilePath, inFileObj.getEncode());
				
				ArrayList<String> inList = null;
				while((inList = inFile.convEscapeStringToList()) != null)
				{
					records.add(inList);
				}
			}
			catch (Exception e)
			{
				// ファイル入力ハンドルを取得できなかった際などにココを通る
				// エラーログ出力し、処理を継続
				commonItem.getLogPrint().printDebugLog(" - ファイルの読み込みに失敗しました");
				// 処理失敗のため、以降でファイル出力処理が走らないよう実行を制限
				execAllowFlg = false;
				commonItem.getLogPrint().printDebugLog(" - 処理をスキップします");
				// エラーパスフラグがOFFの場合は、上位に例外をスローする
				if (!runtimeExceptionIgnoreFlg)
				{
					throw e;
				}
			}
			finally
			{
				if (inFile != null)
				{
					inFile.close();
				}
			}
		}
		
		// データを読込み
		int n = records.size();
		for (int i=0; i < n; i++)
		{
			Map<String, String> keyValMap = new HashMap<String, String>();
			ArrayList<String> rec = records.get(i);
			
			int m = rec.size();
			for (int j=0; j < m; j++) {
				String column = headerColumns.get(j);
				String value = rec.get(j);
				keyValMap.put(column, value);
			}
			// ユニークレコードマップに保存
			String uniqueKey = convertToUniqueKey(keyValMap);
			uniqueRecMap.put(uniqueKey, keyValMap);
			uniqueKeySortArray.add(uniqueKey);
		}

		// 実行ログ
		commonItem.getLogPrint().printDebugLog(" - 実行時例外無視フラグ(" + runtimeExceptionIgnoreFlg + ")");
	}

	/**
	 * 実行時例外無視フラグ設定
	 * 
	 * ※実行時例外発生時にシステムエラーで終了させないようにする
	 * @param flg true: 実行時例外発生時にシステムエラーで終了させないようにする
	 */
	public void setRuntimeExceptionIgnoreFlg(boolean flg)
	{
		runtimeExceptionIgnoreFlg = flg;

		// 実行ログ
		if (commonItem != null && commonItem.getLogPrint() != null)
		{
			commonItem.getLogPrint().printDebugLog(" - 実行時例外無視フラグ設定(" + runtimeExceptionIgnoreFlg + ")");
		}
	}

	/**
	 * 業務ログ出力
	 * @param msgid メッセージID
	 * @param repChar メッセージ引数
	 * @param values 出力対象項目値：ファイル定義で定義されている個数分の値をセットすること（初回出力日時、ログファイル名は除く）
	 * @return ログ出力を行った際はtrueを返す
	 */
	public boolean printBusinessErrorLog(String msgid, String[] repChar, String[] values)
	{
		// 実行許可のチェック
		if (!execAllowFlg)
		{
			// 通常どおりにエラーログ出力
			commonItem.getLogPrint().printBusinessErrorLog(msgid, repChar);
			return true;
		}

		// 実行ログ
		commonItem.getLogPrint().printDebugLog(" - 業務ログ出力");
		
		Map<String, String> keyValMap = new HashMap<String, String>();
		int valuesCount = 0;
		// メンバ変数.出力対象項目の順に項目値リストの値をセットする
		for (int i=0; i < outColumnList.length; i++)
		{
			String columnNm = outColumnList[i];
			// キー項目名："初回出力日時（FIRST_OUTPUT_DTM）"は無視
			if (FirstLogTimeKey.equals(columnNm))
			{
				continue;
			}
			// キー項目名："ログファイル名（LOG_FILE_NM）"は無視
			if (LogFileNm.equals(columnNm))
			{
				continue;
			}
			
			// マップ項目の追加
			try
			{
				keyValMap.put(columnNm, values[valuesCount++]);
			}
			catch (IndexOutOfBoundsException e)
			{
				// (ファイル定義されている項目数)と引数の個数が異なる場合にココを通る
				// エラーログ出力
				commonItem.getLogPrint().printDebugLog(" - ファイル定義されている項目数と引数の個数が異なります("
								+ e.getStackTrace()[0].getClassName() + "."
								+ e.getStackTrace()[0].getMethodName() + ")");
				// エラーパスフラグがOFFの場合は、上位に例外をスローする
				if (!runtimeExceptionIgnoreFlg)
				{
					throw e;
				}
			}
		}
		
		if (!isExists(keyValMap))
		{
			// ログ未出力のため、対象をエラーログ出力
			commonItem.getLogPrint().printBusinessErrorLog(msgid, repChar);
			return true;
		} else {
			// ログ出力済みの場合はココを通る
			return false;
		}
	}

	/**
	 * エラーログ出力
	 * @param msgid メッセージID
	 * @param errObj 例外オブジェクト
	 * @param repChar メッセージ引数
	 * @param values 出力対象項目値：ファイル定義で定義されている個数分の値をセットすること（初回出力日時、ログファイル名は除く）
	 * @return ログ出力を行った際はtrueを返す
	 */
	public boolean printErrMsg(String msgid, Throwable errObj, String[] repChar, String[] values)
	{
		// 実行許可のチェック
		if (!execAllowFlg)
		{
			// 通常どおりにエラーログ出力
			commonItem.getLogPrint().printErrMsg(msgid, errObj, repChar);
			return true;
		}

		// 実行ログ
		commonItem.getLogPrint().printDebugLog(" - エラーログ出力");
		
		Map<String, String> keyValMap = new HashMap<String, String>();
		int valuesCount = 0;
		// メンバ変数.出力対象項目の順に項目値リストの値をセットする
		for (int i=0; i < outColumnList.length; i++)
		{
			String columnNm = outColumnList[i];
			// キー項目名："初回出力日時（FIRST_OUTPUT_DTM）"は無視
			if (FirstLogTimeKey.equals(columnNm))
			{
				continue;
			}
			// キー項目名："ログファイル名（LOG_FILE_NM）"は無視
			if (LogFileNm.equals(columnNm))
			{
				continue;
			}
			
			// マップ項目の追加
			try
			{
				keyValMap.put(columnNm, values[valuesCount++]);
			}
			catch (IndexOutOfBoundsException e)
			{
				// (ファイル定義されている項目数)と引数の個数が異なる場合にココを通る
				// エラーログ出力
				commonItem.getLogPrint().printDebugLog(" - ファイル定義されている項目数と引数の個数が異なります("
						+ e.getStackTrace()[0].getClassName() + "."
						+ e.getStackTrace()[0].getMethodName() + ")");
				// エラーパスフラグがOFFの場合は、上位に例外をスローする
				if (!runtimeExceptionIgnoreFlg)
				{
					throw e;
				}
			}
		}
		
//		if (!isExists(keyValMap))
//		{
//			// ログ未出力のため、対象をエラーログ出力
//			commonItem.getLogPrint().printErrMsg(msgid, errObj, repChar);
//			return true;
//		} else {
//			// ログ出力済みの場合はココを通る
//			return false;
//		}

		// エラーログは常に出力する（同ユニークキーでも例外の内容が変わっている場合があるため）
		commonItem.getLogPrint().printErrMsg(msgid, errObj, repChar);
		boolean existsFlg = true;
		String key = convertToUniqueKey(keyValMap);
		Map<String, String> matchedRec = uniqueRecMap.get(key);
		if (matchedRec == null) {
			// キー値に該当する値が存在しない
			// ユニークレコードマップに追加する
			matchedRec = addKeyValMap(keyValMap);
			existsFlg = false;
		}
		// エラーの情報は最新のログファイルに出力されるため、現在出力対象となっている業務ログのファイル名を指定
		matchedRec.put(LogFileNm, targetLogFileName);
		// マップ上のデータ（レコード）を参照済みとする
		matchedRec.put(AccessedFlgKey, AccessedFlgValue);

		// エラーログは常に出力するが
		// データダンプ（BUSINESS_ERROR_RECORD）は初回のログ出力時（マイナーエラー発生時）しか出力されないため、
		// どのレコード起因で出力されたかがわかるようキー情報をログに出力しておく
		StringBuffer message = new StringBuffer("エラー対象データ:\n");
		for (int i=0; i < KeyNames.length; i++)
		{
			String keyname = KeyNames[i];
			String val = matchedRec.get(keyname);
			message.append("\n").append(keyname).append("=").append(val);
		}
		message.append("\n");
		commonItem.getLogPrint().printErrMsg(JPCBatchMessageConstant.EKKB0960AI, errObj, new String[]{message.toString()});
		
		// 同ユニークキーでユニークレコードマップに居た場合はfalse、居なかった場合はtrueを返す
		return !existsFlg;
	}
	
	/**
	 * ユニークキーマップデータをファイル出力
	 * @throws Exception
	 */
	public void outputList()
		throws Exception
	{
		// 実行許可のチェック
		if (!execAllowFlg)
		{
			return;
		}

		// 実行ログ
		commonItem.getLogPrint().printDebugLog(" - ファイル出力");
		
		try
		{
			// ファイル出力レコードの制限数を取得する
			int recLimitCount = getRecordOutLimitCount(commonItem.getJobid());
			
			JBSbatOutputItem outputBean = new JBSbatOutputItem();
			ArrayList<JBSbatServiceInterfaceMap> outMapList = new ArrayList<JBSbatServiceInterfaceMap>();
			outputBean.setOutMapList(outMapList);
			
			String[] uniqueRecMapKeys = uniqueKeySortArray.toArray(new String[uniqueKeySortArray.size()]);
			
			for (int i=0; i < uniqueRecMapKeys.length; i++)
			{
				String key = uniqueRecMapKeys[i];
	
				Map<String, String> matchedValue = uniqueRecMap.get(key);
				if (matchedValue.get(AccessedFlgKey) == null)
				{
					// 書き出し不要
					continue;
				}
				// ファイル定義項目を出力項目に追加
				JBSbatServiceInterfaceMap outMap = new JBSbatServiceInterfaceMap();
				for (int k=0; k < outColumnList.length; k++)
				{
					outMap.set(outColumnList[k], matchedValue.get(outColumnList[k]));
				}
				
				outMapList.add(outMap);

				// 出力制限数に達した場合はその時点で終了
				if (outMapList.size() >= recLimitCount)
				{
					// OM-2015-0002122 MOD START
					// SYSTEM_INFORMATIONとして業務ログに出力
//					commonItem.getLogPrint().printLogMsg(JPCBatchMessageConstant.CS00031I, "ファイル出力可能な制限レコード数に達しました（アプリケーションプロパティ値：KK_ONE_TIME_LOGW_LINE_OUT_LIMIT）");
					// 実行時に「ワンタイムログ出力部品行出力制限数」に達した場合、シスログにワーニングメッセージを出力する。
					String msgId = JPCBatchMessageConstant.EKKB1260AW;
					String[] repChars = new String[]{"KK_ONE_TIME_LOGW_LINE_OUT_LIMIT"};
					commonItem.getLogPrint().printBusinessErrorLog(msgId, repChars);
					// OM-2015-0002122 MOD END
					break;
				}
			}
	
			JKKBatOutputUtil.editOutFile(commonItem, outputBean, defFileId, keyValFileDir, keyValFileName);
		}
		catch (Exception e)
		{
			// ファイル出力ハンドルを取得できなかった際などにココを通る
			// エラーログ出力し、処理を継続
			commonItem.getLogPrint().printDebugLog(" - ファイルの書込みに失敗しました");
			// エラーパスフラグがOFFの場合は、上位に例外をスローする
			if (!runtimeExceptionIgnoreFlg)
			{
				throw e;
			}
		}
	}
	
	/**
	 * ユニークレコードマップ上の存在確認
	 * @param keyValMap 
	 * @return 存在する場合、true
	 */
	public boolean isExists(Map<String, String> keyValMap)
	{
		// 実行許可のチェック
		if (!execAllowFlg)
		{
			return false;
		}

		String key = convertToUniqueKey(keyValMap);
		
		boolean existsFlg = true;
		Map<String, String> matchedRec = uniqueRecMap.get(key);
		if (matchedRec == null) {
			// キー値に該当する値が存在しない
			// ユニークレコードマップに追加する
			matchedRec = addKeyValMap(keyValMap);
			existsFlg = false;
		}
		else {
			if (commonItem.getLogPrint().chkLogLevel(JBSbatLogUtil.MODE_DEBUG))
			{
				String logFileNm = matchedRec.get(LogFileNm);
				if (logFileNm != null)
				{
					String msg = "エラー情報は次のログを参照してください -> " + logFileNm;
					commonItem.getLogPrint().printDebugLog(msg);
				}
			}
		}

		// マップ上のデータ（レコード）を参照済みとする
		matchedRec.put(AccessedFlgKey, AccessedFlgValue);
		
		return existsFlg;
	}

	/**
	 * KeyValueマップのキー作成
	 * @param keyValMap
	 * @return キー文字列
	 */
	private String convertToUniqueKey(Map<String, String> keyValMap)
	{
		StringBuffer key = new StringBuffer();
		for (int i=0; i < KeyNames.length; i++)
		{
			String column = KeyNames[i];
			String value = keyValMap.get(column);
			key.append(value).append(KEYS_SEP);
		}
		return key.toString();
	}
	
	/**
	 * KeyValueマップの追加
	 * @param keyValMap
	 */
	private Map<String, String> addKeyValMap(Map<String, String> keyValMap)
	{
		Map<String, String> keyValMapTmp = new HashMap<String, String>();
		
		for (int j=0; j < outColumnList.length; j++) {
			String columnNm = outColumnList[j];
			String val = keyValMap.get(columnNm);
			
			// キー項目名が"初回出力日時（FIRST_OUTPUT_DTM）"である場合、当キーの値としてメンバ変数.実行開始日時をセットする。
			if (FirstLogTimeKey.equals(columnNm))
			{
				val = runtimeStartDtm;
			}
			// キー項目名リストにキー："ログファイル名（LOG_FILE_NM）"が含まれる場合、当キーの値としてログファイル名をセットする。
			else if (LogFileNm.equals(columnNm))
			{
				val = targetLogFileName;
			}
			
			keyValMapTmp.put(columnNm, val);
		}
		
		// ユニークレコードマップに追加
		String uniqueKey = convertToUniqueKey(keyValMapTmp);
		uniqueRecMap.put(uniqueKey, keyValMapTmp);
		uniqueKeySortArray.add(uniqueKey);
		
		return keyValMapTmp;
	}

	/**
	 * プロパティファイルより、該当のジョブに対して本プログラムの実行が許可されているかをチェックする
	 * @param jobId ジョブID
	 */
	private void checkExecAllow(String jobId)
	{
		String tgtJobs = JCCBatCommon.getApplicationConst("KK_ONE_TIME_LOGW_EXEC_TGT_JOB");
		if (tgtJobs == null)
		{
			return;
		}
		
		String[] sp = tgtJobs.split(",");
		for (int i=0; i < sp.length; i++)
		{
			if (sp[i].equals(jobId))
			{
				execAllowFlg = true;
				break;
			}
		}
	}
	
	/**
	 * プロパティファイルより、ファイル出力可能な制限レコード数を取得する
	 * @param jobId ジョブID
	 * @return 出力レコード制限となる数値
	 */
	private int getRecordOutLimitCount(String jobId)
	{
		String tgtJobs = JCCBatCommon.getApplicationConst("KK_ONE_TIME_LOGW_LINE_OUT_LIMIT");
		if (tgtJobs == null)
		{
			return DefaultRecordOutLimitCount;
		}
		
		try
		{
			String[] sp = tgtJobs.split(",");
			for (int i=0; i < sp.length; i++)
			{
				String[] sp2 = sp[i].split(":");
				if (sp2.length == 2)
				{
					if (sp2[0].equals(jobId))
					{
						int limitCount = Integer.parseInt(sp2[1]);
						return limitCount;
					}
				}
			}
		}
		catch (Exception e)
		{
			// Nothing...
		}
		return DefaultRecordOutLimitCount;
	}
	
}
