/*
 * FixedLengthRecordReader.java
 * Copyright (C) 2008 Cyber Beans Corporation. All rights reserved.
 */
package jp.co.cybec.cb3.accessor.fileaccess.flat.fixed;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;

import jp.co.cybec.cb3.accessor.fileaccess.RecordFieldConstants;
import jp.co.cybec.cb3.accessor.fileaccess.RecordFieldReaderFormat;
import jp.co.cybec.cb3.accessor.fileaccess.RecordFieldReaderFormatable;
import jp.co.cybec.cb3.accessor.fileaccess.flat.FlatFileRecord;
import jp.co.cybec.cb3.accessor.fileaccess.flat.FlatFileRecordImpl;
import jp.co.cybec.cb3.accessor.fileaccess.flat.FlatFileRecordReaderImpl;
import jp.co.cybec.cb3.exception.AccessorAlreadyOpenedException;
import jp.co.cybec.cb3.exception.AccessorNotOpenException;
import jp.co.cybec.cb3.exception.ConfigFileDefinitionException;
import jp.co.cybec.cb3.exception.ConfigFileIOException;
import jp.co.cybec.cb3.exception.ConfigFileNotFoundException;
import jp.co.cybec.cb3.exception.ConfigFileParseException;
import jp.co.cybec.cb3.exception.DataFileIOException;
import jp.co.cybec.cb3.exception.DataFileNotFoundException;
import jp.co.cybec.cb3.exception.DataFormatException;
import jp.co.cybec.cb3.util.ConfigurationLoader;
import jp.co.cybec.cb3.util.logging.ErrorCodeConstant;
import jp.co.cybec.cb3.util.logging.LogMessageConstant;
import jp.co.cybec.cb3.util.logging.LogMessageFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 固定長ファイルのレコード読み込みクラス。
 * @author Jianming Guo
 */
public class FixedLengthRecordReader extends FlatFileRecordReaderImpl {
	/** ログ */
	private static final Log LOG = LogFactory.getLog(FixedLengthRecordReader.class);

	/** メッセージ */
	private static final LogMessageFactory MESSAGE = new LogMessageFactory(
			LogMessageConstant.CB3_MESSAGE_FILENAME);

	/** コンフィグ：設定ファイル情報 */
	private FixedLengthRecordReaderConfig config = null;

	/** ルールファイルパス */
	private String ruleFilepath = null;

	/** 設定ファイルパス */
	private String configFilepath = null;

	/** 行番号 */
	private int row = 0;

	/**
	 * ファイルをオープンします。
	 *
	 * @param filepath データファイルのパス
	 * @throws AccessorAlreadyOpenedException ファイルがすでにオープンされている場合に発生します。
	 * @throws DataFileNotFoundException ファイルが存在しない時に発生します。
	 * @throws DataFileIOException  レコードの読み込み時のIOエラーにより発生します。
	 */
	@Override
	public void open(String filepath) throws AccessorAlreadyOpenedException,
			DataFileNotFoundException, DataFileIOException {
		config.setFilepath(filepath);
		super.open(filepath);
	}

	/**
	 * 次のレコードを返却します。
	 *
	 * @return 次のレコード。存在しない場合は、nullを返す。
	 * @throws AccessorNotOpenException ファイルがオープンされていない場合に発生します。
	 * @throws DataFileIOException レコードの読み込み時のIOエラーにより発生します。
	 * @throws DataFormatException レコード項目のフォーマットに失敗した場合に発生します。
	 */
	public FlatFileRecord nextRecord() throws AccessorNotOpenException, DataFileIOException, DataFormatException{

		try {
			// ファイルがオープンされていない場合、例外をスローします。
			if (!isOpen()) {
				AccessorNotOpenException ex = new AccessorNotOpenException(MESSAGE.getErrorMessage(
						ErrorCodeConstant.ACCESSOR_FILEACCESS_FLAT_FIXED_001,
						LogMessageConstant.CB3_ACCESSOR_FILEACCESS_001, getFilepath()));
				LOG.info(ex.getMessage(), ex);
				throw ex;
			}

			// ファイルから１行データを読み込む
			String nextLine = super.getBufferedReader().readLine();

			// 行をカウント
			row++;

			// １レコードのデータをフォーマットして、FlatFileRecordを返却する
			return nextLine != null ? parseLine(nextLine) : null;

		} catch (IOException e) {
			DataFileIOException ex = new DataFileIOException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_FLAT_FIXED_002,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_004), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}
	}

	/**
	 * 固定長ファイルの設定ファイルを読み込みます。
	 *
	 * @param key String 設定ファイル名
	 * @throws ConfigFileNotFoundException 設定ファイルが存在しない場合に発生します。
	 * @throws ConfigFileDefinitionException 設定ファイルの間違いにより発生します。
	 * @throws ConfigFileIOException 設定ファイル読み込み時のIOエラーにより発生します。
	 * @throws ConfigFileParseException 設定ファイルのXMLファイル解析が失敗した時に発生します。
	 */
	public void initialize(String key) throws ConfigFileNotFoundException,
		ConfigFileDefinitionException, ConfigFileIOException, ConfigFileParseException {

		// keyより固定長ファイル設定ファイルを読み込む
		config = (FixedLengthRecordReaderConfig) ConfigurationLoader.load(
				getRuleFilepath(), key);

		// 設定ファイルパスを設定
		this.configFilepath = key;

		// encodeのデフォルト値を設定
		if (config.getEncode() == null || config.getEncode().length() == 0) {
			config.setEncode(RecordFieldConstants.SYSTEM_ENCODE);
		}

		// ファイルパスを設定
		super.setFilepath(config.getFilepath());
		// エンコードを設定
		super.setEncode(config.getEncode());
	}

	/**
	 * １レコードのデータをFlatFileRecordに変換します。
	 *
	 * @param nextLine １行のデータ文字列
	 * @return 変換後のFlatFileRecord型レコード
	 * @throws IOException エンコードが正しくない場合に発生します。
	 * @throws DataFormatException レコード項目のフォーマットに失敗した場合に発生します。
	 */
	private FlatFileRecord parseLine(String nextLine) throws IOException, DataFormatException {

		if (nextLine == null || nextLine.length() == 0) {
			return null;
		}

		// レコードマップをインスタンス
		LinkedHashMap<String, String> recordMap = new LinkedHashMap<String, String>();

		// １行データの文字列 → バイト配列に格納
		byte[] byteNextLine = nextLine.getBytes(config.getEncode());

		// 設定ファイルのフィールド情報リストを取得
		List<FixedLengthRecordFieldConfig> fieldList = config.getFieldList();

		// 先頭バイトのインデックス
		int offset = 0;

		// ファイルレコードマップのデータを作成
		for (FixedLengthRecordFieldConfig field : fieldList) {
			// バイト数
			int length = Integer.parseInt(field.getLength());

			// フィールドの値を取得
			String value = null;
			try {
				value = new String(byteNextLine, offset, length, config.getEncode());
				offset = offset + length;
			} catch (IndexOutOfBoundsException e) {
				// バイト配列の範囲外の場合(１行データのバイト数 < 設定ファイルのバイト数の合計)
				DataFormatException ex = new DataFormatException(MESSAGE.getErrorMessage(
						ErrorCodeConstant.ACCESSOR_FILEACCESS_FLAT_FIXED_003,
						LogMessageConstant.CB3_ACCESSOR_FILEACCESS_015, configFilepath, config
								.getFilepath()));
				LOG.info(ex.getMessage(), ex);
				throw ex;
			}

			// フォーマット処理
			if (field.getPatternClass() != null && field.getPatternClass().length() > 0) {
			// パターンクラスが設定された場合
				try {
					// ユーザー定義クラスをインスタンスする
					RecordFieldReaderFormatable fieldFormat = (RecordFieldReaderFormatable) Class.forName(field.getPatternClass()).newInstance();

					// パディングした文字を取り除く
					value = fieldFormat.trim(field.getAlign(), field.getPadding(), value);

					// フォーマットする
					value = fieldFormat.format(field.getPattern(), field.getType(), value);

				} catch (DataFormatException e) {
					throw e;
				} catch (Exception e) {
					DataFormatException ex = new DataFormatException(MESSAGE
							.getErrorMessage(ErrorCodeConstant.ACCESSOR_FILEACCESS_FLAT_FIXED_004,
									LogMessageConstant.CB3_ACCESSOR_FILEACCESS_009, field
											.getPatternClass()));
					LOG.info(ex.getMessage(), ex);
					throw ex;
				}
			} else {
			// パターンクラスが設定されない場合
				// RecordFieldReaderFormatをインスタンスする
				RecordFieldReaderFormat fieldFormat = new RecordFieldReaderFormat(configFilepath, field.getName());

				// パディングした文字を取り除く
				value = fieldFormat.trim(field.getAlign(), field.getPadding(), value);

				// フォーマットする
				value = fieldFormat.format(field.getPattern(), field.getType(), value);
			}

			// レコードマップにセット
			recordMap.put(field.getName(), value);
		}

		// バイト数をチェック
		if (offset != byteNextLine.length) {
			// １行データのバイト数 <> 設定ファイルのバイト数合計の場合
			DataFormatException ex = new DataFormatException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_FLAT_FIXED_005,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_015, configFilepath, config
							.getFilepath()));
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		return new FlatFileRecordImpl(recordMap);
	}

	/**
	 * ルールファイルパスを返します。<br>
	 * @return ルールファイルパス
	 */
	public String getRuleFilepath() {
		if (ruleFilepath == null) {
			ruleFilepath = RecordFieldConstants.FIXED_LENGTH_FILE_READER_RULE_FILE_NAME;
		}
		return ruleFilepath;
	}

	/**
	 * ルールファイルパスを設定します。<br>
	 * ルールファイルパスを設定しない場合、およびnullを設定した場合はデフォルトのパス
	 * ({@link RecordFieldConstants#FIXED_LENGTH_FILE_READER_RULE_FILE_NAME})が使用されます。
	 * @param path ルールファイルパス
	 */
	public void setRuleFilepath(String path) {
		ruleFilepath = path;
	}
}
