/*
 * FixedLengthRecordWriter.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.io.UnsupportedEncodingException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import jp.co.cybec.cb3.accessor.fileaccess.RecordFieldConstants;
import jp.co.cybec.cb3.accessor.fileaccess.RecordFieldWriterFormat;
import jp.co.cybec.cb3.accessor.fileaccess.RecordFieldWriterFormatable;
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.FlatFileRecordWriterImpl;
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 FixedLengthRecordWriter extends FlatFileRecordWriterImpl {
	/** ログ */
	private static final Log LOG = LogFactory.getLog(FixedLengthRecordWriter.class);

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

	/** コンフィグ：設定ファイル情報 */
	private FixedLengthRecordWriterConfig 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);
	}

	/**
	 * 1行分レコードをファイルに書き込みます。
	 *
	 * @param record 1行レコード
	 * @throws AccessorNotOpenException ファイルがオープンされていない場合に発生します。
	 * @throws DataFileIOException レコードの書き込み時のIOエラーにより発生します。
	 * @throws DataFormatException レコード項目のフォーマットに失敗した場合に発生します。
	 */
	public void write(FlatFileRecord record) throws AccessorNotOpenException, DataFileIOException,  DataFormatException {
		// ファイルがオープンされていない場合、例外をスローします。
		if (!isOpen()) {
			AccessorNotOpenException ex = new AccessorNotOpenException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_FLAT_FIXED_006,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_001, getFilepath()));
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		// 1行分レコードをファイルに書き込みます。
		writeNext(record);
	}

	/**
	 * 可変長ファイルの設定ファイルを読み込みます。
	 *
	 * @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 = (FixedLengthRecordWriterConfig) ConfigurationLoader.load(
				getRuleFilepath(), key);

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

		// appendのデフォルト値を設定
		if (config.getAppend() == null || config.getAppend().length() == 0) {
			config.setAppend(RecordFieldConstants.FILE_OPEN_APPEND_FALSE);
		}
		// encodeのデフォルト値を設定
		if (config.getEncode() == null || config.getEncode().length() == 0) {
			config.setEncode(RecordFieldConstants.SYSTEM_ENCODE);
		}
		// lineSeparatorのデフォルト値を設定
		if (config.getLineSeparator() == null || config.getLineSeparator().length() == 0) {
			config.setLineSeparator(RecordFieldConstants.SYSTEM_LINE_SEPARATOR);
		} else {
			config.setLineSeparator(config.getLineSeparator().replaceAll("\\\\r", "\r"));
			config.setLineSeparator(config.getLineSeparator().replaceAll("\\\\n", "\n"));
		}

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

	/**
	 * 1行分レコードをファイルに書き込みます。
	 *
	 * @param nextLine 1行レコード
	 * @throws DataFileIOException レコードの読み込み時のIOエラーにより発生します。
	 * @throws DataFormatException レコード項目のフォーマットに失敗した場合に発生します。
	 */
	private void writeNext(FlatFileRecord nextLine) throws DataFileIOException, DataFormatException {
		if (nextLine == null) {
			return;
		}
		// レコードマップをセット
		Map<String, String> fileRecordMap = nextLine.toMap();

		try {
			// レコード文字列を作成
			String recordString = makeRecordString(fileRecordMap);

			// レコード文字列をファイルに書き込む
			super.getBufferedWriter().write(recordString);
			// 改行文字を書き込む
			super.getBufferedWriter().write(config.getLineSeparator());

			// ストリームをフラッシュする
			super.getBufferedWriter().flush();

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

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

	/**
	 * FlatFileRecordデータを作成します。
	 *
	 * @param record データマップ
	 * @return 作成したFlatFileRecordデータ
	 */
	public FlatFileRecord createFlatFileRecord(Map<String, String> record) {

		if (record == null) {
			return null;
		}

		// ファイルレコードマップをインスタンス
		LinkedHashMap<String, String> fileRecordMap = new LinkedHashMap<String, String>();
		// 設定ファイルのフィールド情報リストを取得
		List<FixedLengthRecordFieldConfig> fieldList = config.getFieldList();
		// ファイルレコードマップのデータを作成
		for (FixedLengthRecordFieldConfig field : fieldList) {
			fileRecordMap.put(field.getName(), record.get(field.getName()));
		}

		return new FlatFileRecordImpl(fileRecordMap);
	}

	/**
	 * フィールド値をフォーマットし、書込み用のレコード文字列を作成します。
	 *
	 * @param fileRecordMap レコードマップ
	 * @return 作成したレコード文字列
	 * @throws DataFormatException レコード項目のフォーマットに失敗した場合に発生します。
	 * @throws UnsupportedEncodingException 文字のエンコーディングがサポートされていない場合に発生します。
	 */
	private String makeRecordString(Map<String, String> fileRecordMap) throws DataFormatException, UnsupportedEncodingException {
		StringBuffer recordString = new StringBuffer();

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

		// ファイルレコードマップのデータを作成
		for (FixedLengthRecordFieldConfig field : fieldList) {
			// フィールド値を取得
			String value = fileRecordMap.get(field.getName());

			// バイト数の設定がない場合
			if (field.getLength() == null || field.getLength().length() == 0) {
				DataFormatException ex = new DataFormatException(MESSAGE.getErrorMessage(
						ErrorCodeConstant.ACCESSOR_FILEACCESS_FLAT_FIXED_008,
						LogMessageConstant.CB3_ACCESSOR_FILEACCESS_016, configFilepath, field
								.getName()));
				LOG.info(ex.getMessage(), ex);
				throw ex;
			}

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

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

					// パディングする
					value = fieldFormat.padding(field.getLength(), field.getAlign(), field.getPadding(), value);

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

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

				// パディングする
				value = fieldFormat.padding(field.getLength(), field.getAlign(), field.getPadding(), value);
			}

			// フォーマットしたフィールド値をチェック
			if (value == null || value.getBytes(config.getEncode()).length != Integer.parseInt(field.getLength())) {
				DataFormatException ex = new DataFormatException(MESSAGE.getErrorMessage(
						ErrorCodeConstant.ACCESSOR_FILEACCESS_FLAT_FIXED_010,
						LogMessageConstant.CB3_ACCESSOR_FILEACCESS_017, configFilepath, field
								.getName(), field.getLength(), fileRecordMap.get(field.getName()),
						value));
				LOG.info(ex.getMessage(), ex);
				throw ex;
			}

			// フォーマットしたフィールド値をセット
			recordString.append(value);
		}

		return recordString.toString();
	}

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

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