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

import org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.fukurou.system.Closer ;
import static org.opengion.fukurou.system.HybsConst.CR ;		// 6.1.0.0 (2014/12/26)
import static org.opengion.fukurou.system.HybsConst.FS ;		// 6.1.0.0 (2014/12/26)

import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import java.util.regex.Pattern;
import java.util.regex.Matcher;

/**
 * DBTableReport インターフェース を実装したネイティブEXCEL形式で出力するクラスです。
 * AbstractDBTableReport を継承していますので，writeReport() のみオーバーライドして，
 * 固定長文字ファイルの出力機能を実現しています。
 *
 * @og.group 帳票システム
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class DBTableReport_Excel extends AbstractDBTableReport {

	private static final String EXCEL_FILE_EXT	  = ".xls";
	private static final Pattern PATTERN_KEY =
			  Pattern.compile("\\{@((\\w+?)(?:_(\\d+?))?)\\}", Pattern.MULTILINE);

	// POIの解析した式の中に変な属性が付けられて、これを取り除く(patternExcludeInFormula)
	private static final Pattern PATTERN_EXIN =
			  Pattern.compile("ATTR\\(semiVolatile\\)", Pattern.MULTILINE);

	private HSSFWorkbook wb	;

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
	 */
	public DBTableReport_Excel() {
		super();		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
	}

	/**
	 * DBTableModel から データを作成して,PrintWriter に書き出します。
	 *
	 */
	@Override
	public void writeReport() {
		setHeaderFooter();
		initReader();
		initWriter();
		changeSheet();
		close();
	}

	/**
	 * POIFSFileSystem を、初期化します。
	 * これは、雛型ファイルの終端まで読取り、処理した場合、もう一度
	 * 初めから読み込みなおす処理を行います。
	 * 基本的に、書き込みも初期化する必要があります。
	 *
	 * メモリ上に読み込んで、繰り返し利用するかどうかは、実装依存です。
	 *
	 */
	@Override
	protected void initReader() {
		if( null != wb ) { wb = null; }

		FileInputStream  istream = null;
		try {
			istream = new FileInputStream(templateFile);
			final POIFSFileSystem fs = new POIFSFileSystem(istream);
			wb = new HSSFWorkbook(fs);
		}
		catch( IOException ex ) {
			final String errMsg = "ファイル名がオープン出来ませんでした。"
						+ CR
						+ "  File:" + templateFile;
			throw new HybsSystemException( errMsg,ex );		// 3.5.5.4 (2004/04/15) 引数の並び順変更
		}
		finally {
			Closer.ioClose( istream );	// 4.0.0 (2006/01/31) close 処理時の IOException を無視
		}
	}

	/**
	 * FileOutputStream を、初期化します。
	 * これは、雛型ファイルを終端まで読取り、処理した場合、出力ファイル名を
	 * 変えて、別ファイルとして出力する為のものです。
	 * 基本的に、読取も初期化する必要があります。
	 *
	 * 現在の所、POIはメモリ上にExcelファイルを作成する為、作成したファイルの書く込むを
	 * ファイル閉じる時点に伸ばされます。
	 *
	 */
	@Override
	protected void initWriter() {
		// ここでは処理を行いません。
	}

	/**
	 * リーダー、ライターの終了処理を行います。
	 * このメソッドが呼ばれたタイミングで、実際にファイル出力を行います。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
	 *
	 */
	protected void close() {
		// 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
		if( wb == null ) {
			final String errMsg = "#initReader()を先に実行しておいてください。" ;
			throw new OgRuntimeException( errMsg );
		}

		final String filename = htmlDir + FS + htmlFileKey + EXCEL_FILE_EXT ;

		FileOutputStream fileOut = null;
		try {
			// Write the output to a file
			fileOut = new FileOutputStream(filename);
			wb.write(fileOut);
		}
		catch( IOException ex ) {
			wb = null;
			final String errMsg = "ファイルが出力(書き込み)出来ませんでした。"
						+ CR
						+ "  File:" + filename;
			throw new HybsSystemException( errMsg,ex );		// 3.5.5.4 (2004/04/15) 引数の並び順変更
		}
		finally {
			Closer.ioClose( fileOut );	// 4.0.0 (2006/01/31) close 処理時の IOException を無視
		}
	}

	/**
	 * Excelの雛型をコピーして、そのシートに帳票データを埋め込みます。
	 * いろいろな属性がある所に、適切に対応していく予定。
	 * 各サブクラスで実装してください。
	 *
	 * @og.rev 4.3.4.0 (2008/12/01) POI3.2対応
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
	 *
	 */
	protected void changeSheet() {
		// 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
		if( wb == null ) {
			final String errMsg = "#initReader()を先に実行しておいてください。" ;
			throw new OgRuntimeException( errMsg );
		}

		final HSSFSheet patternSheet = wb.getSheetAt(0);
		while(!rowOver) {
			final HSSFSheet sheet2 = wb.cloneSheet(0);
	//		HSSFRow oRow2;
			final int nFirstRow = sheet2.getFirstRowNum();
			final int nLastRow  = sheet2.getLastRowNum();
	//		int nTotalRows = patternSheet.getPhysicalNumberOfRows();
			for( int nIndexRow=nFirstRow; nIndexRow<=nLastRow; nIndexRow++ ) {
				final HSSFRow oRow = patternSheet.getRow(nIndexRow);
				if( null != oRow ) {
	//				int nTotalCells = oRow.getPhysicalNumberOfCells();
					// 4.3.4.0 (2008/12/01) POI3.2対応。shortをintにする。
					// short nFirstCell = oRow.getFirstCellNum();
					// short nLastCell  = oRow.getLastCellNum();
					final int nFirstCell = oRow.getFirstCellNum();
					final int nLastCell  = oRow.getLastCellNum();
					for( int nIndexCell=nFirstCell; nIndexCell<=nLastCell; nIndexCell++ ) {
						final HSSFCell oCell = oRow.getCell(nIndexCell);
						if( null != oCell ) { changeCell(oCell);}
					}
				}
			}
		}
	}

	/**
	 * セル情報を変更します。
	 *
	 * @og.rev 4.3.4.0 (2008/12/01) POI3.2対応
	 *
	 * @param oCell HSSFCellオブジェクト
	 */
	protected void changeCell(final HSSFCell oCell) {
		String strText;
		HSSFRichTextString richText;
		final int nCellType = oCell.getCellType();
		switch(nCellType) {
			case HSSFCell.CELL_TYPE_FORMULA:
				strText = changeData(changeFormulaAttr(oCell.getCellFormula()));
				if( null != strText ) {
	//				oCell.setCellType(HSSFCell.CELL_TYPE_FORMULA);
	//				oCell.setEncoding(HSSFCell.ENCODING_UTF_16);
					oCell.setCellFormula(strText);
				}
				break;
			case HSSFCell.CELL_TYPE_STRING:
	// POI3.0	strText =  changeData(oCell.getStringCellValue());
				richText = oCell.getRichStringCellValue();
				strText =  changeData(richText.getString());
				if( null != strText ) {
	//				oCell.setCellType(HSSFCell.CELL_TYPE_STRING);
	// POI3.0		oCell.setEncoding(HSSFCell.ENCODING_UTF_16);
	// POI3.2		oCell.setCellValue( strText );	// POI3.0 Deprecation
					oCell.setCellValue( new HSSFRichTextString(strText) );
				}
				break;
	//		case HSSFCell.CELL_TYPE_NUMERIC:
	//			break;
	//		case HSSFCell.CELL_TYPE_BOOLEAN:
	//			break;
	//		case HSSFCell.CELL_TYPE_ERROR:
	//			break;
			default :
				break;
		}
	}

	/**
	 * POIで解釈したExcel式の中の変な属性を加工して、出力します。
	 * いろいろな属性がある所に、適切に対応していく予定。
	 * 各サブクラスで実装してください。
	 *
	 * @param	inLine	入力文字列
	 *
	 * @return	出力文字列
	 */
	protected String changeFormulaAttr( final String inLine ) {
		// rowOver で、かつ ページブレークかページエンドカットの場合、処理終了。
		final Matcher  matcher = PATTERN_EXIN.matcher(inLine);
		return matcher.find() ? matcher.replaceAll("") : inLine;
	}

	/**
	 * 入力文字列 を加工して、出力します。
	 * データをテーブルモデルより読み取り、値をセットします。
	 * 各サブクラスで実装してください。
	 *
	 * @param	inLine	入力文字列
	 *
	 * @return	出力文字列. 文字列の変換は要らない場合、nullを返します
	 */
	@Override
	protected String changeData( final String inLine ) {
		boolean bFind = false;

		// rowOver で、かつ ページブレークかページエンドカットの場合、処理終了。
		final Matcher  matcher = PATTERN_KEY.matcher(inLine);
		final StringBuffer sb = new StringBuffer();	// Matcher.appendTail( StringBuffer ) の為

		while (matcher.find()) {
			matcher.appendReplacement(sb, getValue(matcher.group( 1 )));
			bFind = true;
		}

		if( bFind ) {
			matcher.appendTail(sb);
			return sb.toString();
		}
		else {
			return null;
		}
	}

	/**
	 * 入力文字列 を読み取って、出力します。
	 * tr タグを目印に、１行(trタグ間)ずつ取り出します。
	 * 読み取りを終了する場合は、null を返します。
	 * 各サブクラスで実装してください。
	 * ※ このクラスでは実装されていません。
	 *
	 * @return	出力文字列
	 */
	@Override
	protected String readLine() {
		throw new UnsupportedOperationException();
	}

	/**
	 * 入力文字列 を読み取って、出力します。
	 * 各サブクラスで実装してください。
	 * ※ このクラスでは実装されていません。
	 *
	 * @param line 入力文字列
	 */
	@Override
	protected void println( final String line ) {
		throw new UnsupportedOperationException();
	}
}
