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

import java.util.ArrayList;
import java.util.List;
import java.util.Map;												// 8.0.3.0 (2021/12/17)
import java.util.HashMap;											// 8.0.3.0 (2021/12/17)

import org.opengion.hayabusa.common.HybsSystemException;
import static org.opengion.fukurou.system.HybsConst.CR ;			// 8.0.3.0 (2021/12/17)
// import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 8.0.3.0 (2021/12/17)

import org.opengion.hayabusa.report2.TagParser.SplitKey;

/**
 * ｼｰﾄ単位のcontent.xmlを管理するためのｸﾗｽです｡
 * ｼｰﾄのﾍｯﾀﾞｰ､行の配列､ﾌｯﾀｰ及びｼｰﾄ名を管理します｡
 *
 * 7.0.1.5 (2018/12/10)
 *   FORMAT_LINEでは､同一行をｺﾋﾟｰするため､ｾﾙの選択行(A5とか$C7とか)までｺﾋﾟｰされ
 *   行ﾚﾍﾞﾙの計算が出来ません｡その場合は､INDIRECT(ADDRESS(ROW();列番号))関数を
 *   使用することでｾﾙのｱﾄﾞﾚｽが指定可能です｡
 *   列番号は､A=1 です｡
 *   ※ OpenOffice系は､区切り文字が『;』 EXCELの場合は､『,』 要注意
 *
 * ※ 繰り返しを使用する場合で､ﾍｯﾀﾞｰ部分の印刷領域を繰り返したい場合は､
 *    1.｢書式(O)｣-｢印刷範囲(N)｣-｢編集(E)｣で｢印刷範囲の編集｣ﾀﾞｲｱﾛｸﾞを表示
 *    2.｢繰り返す行｣の右の方にある矢印のついたﾎﾞﾀﾝをｸﾘｯｸ
 *    3.ﾍﾟｰｼﾞごとに表示したい行をﾄﾞﾗｯｸして指定する
 *        (ﾃｷｽﾄﾎﾞｯｸｽに$1:$2などと直接入力しても良い->$1:$2の場合であれば1-2行目が繰り返し印刷される)
 *
 * 8.0.3.0 (2021/12/17)
 *    {&#064;FORMATLINE} を指定した行は､BODY(GE51)行のﾌｫｰﾏｯﾄを指定できます｡
 *    {&#064;FORMATLINE_1}で､GE51のKBTEXT=B1 で指定した行をひな形にします｡
 *    {&#064;FORMATLINE_2}で､GE51のKBTEXT=B2 です｡
 *    引数の数字を指定しない場合は､KBTEXT=B です｡
 *    {&#064;DUMMYLINE} は､先のﾌｫｰﾏｯﾄ行をその行と交換して出力します｡
 *    ただし､ﾃﾞｰﾀが存在しない場合は､このDUMMYLINEそのものが使用されます｡
 *    {&#064;COPYLINE} は､先のﾌｫｰﾏｯﾄ行をﾃﾞｰﾀの数だけ繰り返しその場にｺﾋﾟｰします｡
 *    ｲﾒｰｼﾞ的には､DUMMYLINE は､1ﾍﾟｰｼﾞ分のﾌｫｰﾏｯﾄを指定する場合､COPYLINE は
 *    無制限の連続帳票を想定しています｡
 *
 * @og.group 帳票ｼｽﾃﾑ
 *
 * @version  4.0
 * @author   Hiroki.Nakamura
 * @since    JDK1.6
 */
class OdsSheet {

	//======== content.xmlのﾊﾟｰｽで使用 ========================================

	/* 行の開始終了ﾀｸﾞ */
	private static final String ROW_START_TAG = "<table:table-row ";
	private static final String ROW_END_TAG = "</table:table-row>";

	/* ｼｰﾄ名を取得するための開始終了文字 */
	private static final String SHEET_NAME_START = "table:name=\"";
//	private static final String SHEET_NAME_END = "\"";
	private static final String END_KEY = "\"";				// 8.0.3.0 (2021/12/17)

	/* 変数定義の開始終了文字及び区切り文字 */
	private static final String VAR_START = "{@";
	private static final String VAR_END = "}";
//	private static final String VAR_CON = "_";

	/* ﾌｫｰﾏｯﾄﾗｲﾝ文字列 5.0.0.2 (2009/09/15) */
	private static final String FORMAT_LINE = "FORMATLINE";	// 8.0.3.0 (2021/12/17)

	/* ﾀﾞﾐｰﾗｲﾝ文字列 8.0.3.0 (2021/12/17) */
	private static final String DUMMY_LINE = "DUMMYLINE";	// 8.0.3.0 (2021/12/17)

	/* ｺﾋﾟｰﾗｲﾝ文字列 8.0.3.0 (2021/12/17) */
	private static final String COPY_LINE = "COPYLINE";		// 8.0.3.0 (2021/12/17)

	private final List<String>			sheetRows	= new ArrayList<>();
	private final Map<String,String>	rowsMap		= new HashMap<>();
	private int			offsetCnt = -1;		// 8.0.3.0 (2021/12/17) FORMAT_LINE が最初に現れた番号
	private String[]	bodyTypes ;			// 8.0.3.0 (2021/12/17) 行番号に対応した､ﾎﾞﾃﾞｨﾀｲﾌﾟ(KBTEXT)配列

	private String		sheetHeader;
	private String		sheetFooter;
	private String		sheetName;
	private String		origSheetName;
	private String		confSheetName;

	/**
	 * ﾃﾞﾌｫﾙﾄｺﾝｽﾄﾗｸﾀｰ
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
	 */
	public OdsSheet() { super(); }		// これも､自動的に呼ばれるが､空のﾒｿｯﾄﾞを作成すると警告されるので､明示的にしておきます｡

	/**
	 * ｼｰﾄを行単位に分解します｡
	 *
	 * @og.rev 5.0.0.2 (2009/09/15) ﾎﾞﾃﾞｨ部のｶｳﾝﾄを引数に追加し､LINECOPY機能実装｡
	 * @og.rev 5.2.1.0 (2010/10/01) ｼｰﾄ名定義対応
	 * @og.rev 8.0.3.0 (2021/12/17) COPY_LINE機能の追加
	 *
	 * @param sheet ｼｰﾄ名
	 * @param bodyTypes 行番号に対応した､ﾎﾞﾃﾞｨﾀｲﾌﾟ(KBTEXT)配列
	 */
//	public void analyze( final String sheet, final int bodyRowCount ) {
	public void analyze( final String sheet, final String[] bodyTypes ) {
		this.bodyTypes = bodyTypes ;

		final String[] tags = TagParser.tag2Array( sheet, ROW_START_TAG, ROW_END_TAG );
		sheetHeader = tags[0];
		sheetFooter = tags[1];
		for( int i=2; i<tags.length; i++ ) {
//			sheetRows.add( tags[i] );
//			lineCopy( tags[i], bodyRowCount );	// 5.0.0.2 (2009/09/15)
			lineCopy( tags[i] );				// 8.0.3.0 (2021/12/17)
		}

//		sheetName = TagParser.getValueFromTag( sheetHeader, SHEET_NAME_START, SHEET_NAME_END );
		sheetName = TagParser.getValueFromTag( sheetHeader, SHEET_NAME_START, END_KEY );	// 8.0.3.0 (2021/12/17)
		origSheetName = sheetName;

		confSheetName = null;
		if( sheetName != null ) {
			final int cnfIdx = sheetName.indexOf( "__" );
			if( cnfIdx > 0 && !sheetName.endsWith( "__" ) ) {
				confSheetName = sheetName.substring( cnfIdx + 2 );
				sheetName = sheetName.substring( 0, cnfIdx );
			}
		}
	}

	/**
	 * ﾗｲﾝｺﾋﾟｰに関する処理を行います｡
	 *
	 * {&#064;LINECOPY}が存在した場合に､ﾃｰﾌﾞﾙﾓﾃﾞﾙ分だけ
	 * 行をｺﾋﾟｰします｡
	 * その際､{&#064;XXX_番号}の番号をｶｳﾝﾄｱｯﾌﾟしてｺﾋﾟｰします｡
	 *
	 * 整合性等のｴﾗｰﾊﾝﾄﾞﾘﾝｸﾞはこのﾒｿｯﾄﾞでは行わず､
	 * 実際のﾊﾟｰｽ処理中で行います｡
	 *
	 * 7.0.1.5 (2018/12/10)
	 *   LINECOPYでは､同一行をｺﾋﾟｰするため､ｾﾙの選択行(A5とか$C7とか)までｺﾋﾟｰされ
	 *   行ﾚﾍﾞﾙの計算が出来ません｡その場合は､INDIRECT(ADDRESS(ROW(),列番号))関数を
	 *   使用することでｾﾙのｱﾄﾞﾚｽが指定可能です｡
	 *   列番号は､A=1 です｡
	 *
	 * @og.rev 5.0.0.2 (2009/09/15) 追加
	 * @og.rev 5.1.8.0 (2010/07/01) ﾊﾟｰｽ処理の内部実装を変更
	 * @og.rev 7.0.1.5 (2018/12/10) LINECOPYでの注意(JavaDocのみ追記)
	 * @og.rev 8.0.3.0 (2021/12/17) TagParser.SplitKey#incrementKey(int) に処理を移します｡
	 * @og.rev 8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。
	 *
	 * @param rowStr	行ﾃﾞｰﾀ
	 * @param rowCount	ｶｳﾝﾀ
	 */
//	private void lineCopy( final String rowStr, final int rowCount ) {
	private void lineCopy( final String rowStr ) {
		// FORMAT_LINE を見つけて､引数をｷｰにﾏｯﾌﾟに登録します｡
		final String cpLin = TagParser.splitSufix( rowStr,FORMAT_LINE );
		if( cpLin != null ) {
			if( offsetCnt < 0 ) { offsetCnt = sheetRows.size(); }	// 初めてあらわれた位置
			final String tmp = rowsMap.get( "B" + cpLin );
			if( tmp == null ) {
				rowsMap.put( "B" + cpLin , rowStr );		// ﾌｫｰﾏｯﾄのｷｰは､"B" + ｻﾌｨｯｸｽ
	//			sheetRows.add( rowStr );					// 8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。
			}
			else {
				// ｾﾙ結合時に､複数行を1行に再設定する｡
				rowsMap.put( "B" + cpLin , tmp + rowStr );	// ﾌｫｰﾏｯﾄのｷｰは､"B" + ｻﾌｨｯｸｽ
			}
			return;
		}

		// DUMMY_LINE を見つける｡
		final int st1 = rowStr.indexOf( VAR_START + DUMMY_LINE );
		if( st1 >= 0 ) {						// ｷｰが見つかった場合
			if( offsetCnt < 0 ) { offsetCnt = sheetRows.size(); }	// 初めてあらわれた位置
			sheetRows.add( rowStr );			// DUMMY_LINE を登録
			return ;
		}

		// COPY_LINE を見つける｡
		final int st2 = rowStr.indexOf( VAR_START + COPY_LINE );
		if( st2 >= 0 ) {						// ｷｰが見つかった場合
			if( offsetCnt < 0 ) { offsetCnt = sheetRows.size(); }	// 初めてあらわれた位置

			// COPY_LINEは､その場に全件ｺﾋﾟｰします(行数を確保するため)
			for( int row=0; row<bodyTypes.length; row++ ) {
				sheetRows.add( rowStr );		// COPY_LINE を登録
			}
			return ;
		}

		sheetRows.add( rowStr );				// rowStr を登録(通常行)

//		// この段階で存在しなければ即終了
//		final int lcStr = row.indexOf( VAR_START + LINE_COPY );
////		if( lcStrOffset < 0 ) { return; }
//		if( lcStr < 0 ) { sheetRows.add( row ); return 1; }
//		final int lcEnd = row.indexOf( VAR_END, lcStr );
////		if( lcEndOffset < 0 ) { return; }
//		if( lcEnd < 0 ) { sheetRows.add( row ); return 1; }
//
//		final StringBuilder lcStrBuf = new StringBuilder( row );
//		final String lcKey = TagParser.checkKey( row.substring( lcStr + VAR_START.length(), lcEnd ), lcStrBuf );
////		if( lcKey == null || !LINE_COPY.equals( lcKey ) ) { return; }
//		final SplitKey cpKey = new SplitKey( lcKey );		// 8.0.3.0 (2021/12/17)
//		final int copyCnt = cpKey.count( rowCount );

//		// 存在すればﾃｰﾌﾞﾙﾓﾃﾞﾙ行数-1回ﾙｰﾌﾟ(自身を除くため)
//		for( int i=1; i<rowCount; i++ ) {
//		// 存在すればﾃｰﾌﾞﾙﾓﾃﾞﾙ行数回ﾙｰﾌﾟ(自身も含める必要がある)
//		for( int i=0; i<copyCnt; i++ ) {					// {@LINECOPY_回数} で､繰り返し回数指定
//			final int cRow = i;								// final 宣言しないと無名ｸﾗｽに設定できない｡
//			final String rowStr = new TagParser() {
//				/**
//				 * 開始ﾀｸﾞから終了ﾀｸﾞまでの文字列の処理を定義します｡
//				 *
//				 * @param str 開始ﾀｸﾞから終了ﾀｸﾞまでの文字列(開始ﾀｸﾞ･終了ﾀｸﾞを含む)
//				 * @param buf 出力を行う文字列ﾊﾞｯﾌｧ
//				 * @param offset 終了ﾀｸﾞのｵﾌｾｯﾄ(ここでは使っていません)
//				 */
//				@Override
//				protected void exec( final String str, final StringBuilder buf, final int offset ) {
//					String key = TagParser.checkKey( str, buf );
//					if( key.indexOf( '<' ) >= 0 ){
//						final String errMsg = "[ERROR]SHEET:{@と}の整合性が不正です｡" + CR
//											+ "変数内の特定の文字列に書式設定がされている可能性があります｡ｷｰ=" + key;
//						throw new HybsSystemException( errMsg );
//					}
//					final SplitKey spKey = new SplitKey( key );		// 8.0.3.0 (2021/12/17)
////					buf.append( VAR_START ).append( incrementKey( key, cRow ) ).append( VAR_END );
//					buf.append( VAR_START ).append( spKey.incrementKey( cRow ) ).append( VAR_END );
//				}
//			}.doParse( lcStrBuf.toString(), VAR_START, VAR_END, false );
//
//			sheetRows.add( rowStr );
//		}
//		return copyCnt;
	}

//	/**
//	 * XXX_番号の番号部分を引数分追加して返します｡
//	 * 番号部分が数字でない場合や､_が無い場合はそのまま返します｡
//	 *
//	 * @og.rev 5.0.0.2 LINE_COPYで利用するために追加
//	 * @og.rev 8.0.3.0 (2021/12/17) TagParser.SplitKey#incrementKey(int) に処理を移します｡
//	 *
//	 * @param key	ｷｰ文字列
//	 * @param inc	ｶｳﾝﾀ部
//	 *
//	 * @return 変更後ｷｰ
//	 */
//	private String incrementKey( final String key, final int inc ) {
//		final int conOffset = key.lastIndexOf( VAR_CON );
//		if( conOffset < 0 ) { return key; }
//
//		final String name = key.substring( 0, conOffset );
//		int rownum = -1;
//		try {
//			rownum = Integer.parseInt( key.substring( conOffset + VAR_CON.length(), key.length() ) );		// 6.0.2.4 (2014/10/17) ﾒｿｯﾄﾞ間違い
//		}
//		// ｴﾗｰが起きてもなにもしない｡
//		catch( final NumberFormatException ex ) {
//			// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid empty catch blocks
//			final String errMsg = "Not incrementKey. KEY=[" + key + "] " + ex.getMessage() ;
//			System.err.println( errMsg );
//		}
//
//		// ｱﾝﾀﾞｰｽｺｱ後が数字に変換できない場合はﾍｯﾀﾞﾌｯﾀとして認識
//		if( rownum < 0 ){ return key; }
//		else			{ return name + VAR_CON + (rownum + inc) ; }
//	}

	/**
	 * ｼｰﾄのﾍｯﾀﾞｰ部分を返します｡
	 *
	 * @return ﾍｯﾀﾞｰ
	 */
	public String getHeader() {
		return sheetHeader;
	}

	/**
	 * ｼｰﾄのﾌｯﾀｰ部分を返します｡
	 *
	 * @return ﾌｯﾀｰ
	 */
	public String getFooter() {
		return sheetFooter;
	}

	/**
	 * ｼｰﾄ名称を返します｡
	 *
	 * @return ｼｰﾄ名称
	 */
	public String getSheetName() {
		return sheetName;
	}

	/**
	 * 定義済ｼｰﾄ名称を返します｡
	 *
	 * @og.rev 5.2.1.0 (2010/10/01) ｼｰﾄ名定義対応
	 *
	 * @return 定義済ｼｰﾄ名称
	 */
	public String getConfSheetName() {
		return confSheetName;
	}

	/**
	 * 定義名変換前のｼｰﾄ名称を返します｡
	 *
	 * @og.rev 5.2.1.0 (2010/10/01) ｼｰﾄ名定義対応
	 *
	 * @return 定義済ｼｰﾄ名称
	 */
	public String getOrigSheetName() {
		return origSheetName;
	}

//	/**
//	 * ｼｰﾄの各行を配列で返します｡
//	 *
//	 * @og.rev 4.3.1.1 (2008/08/23) あらかじめ､必要な配列の長さを確保しておきます｡
//	 * @og.rev 8.0.3.0 (2021/12/17) 廃止
//	 *
//	 * @return ｼｰﾄの各行の配列
//	 * @og.rtnNotNull
//	 */
//	public String[] getRows() {
//		return sheetRows.toArray( new String[sheetRows.size()] );
//	}

	/**
	 * ｼｰﾄの行を返します｡
	 *
	 * @og.rev 8.0.3.0 (2021/12/17) 新規追加
	 * @og.rev 8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。
	 *
	 * @param idx		ｼｰﾄ内での行番号
	 * @param baseRow	TableModelのﾍﾞｰｽ行番号
	 *
	 * @return ｼｰﾄの行
	 * @og.rtnNotNull
	 */
	public String getRow( final int idx, final int baseRow ) {
		final String rowStr = sheetRows.get( idx );

//		8.1.1.1 (2022/02/18) FORMAT_LINEは、無視します。
//		final boolean useFmt = rowStr.contains( VAR_START + FORMAT_LINE )
//							|| rowStr.contains( VAR_START + DUMMY_LINE )
//							|| rowStr.contains( VAR_START + COPY_LINE ) ;

		final boolean useFmt = rowStr.contains( VAR_START + DUMMY_LINE )
							|| rowStr.contains( VAR_START + COPY_LINE ) ;

		if( useFmt ) {				// ｷｰが見つかった場合
			final int row = idx-offsetCnt+baseRow;

			final String dummy = row < bodyTypes.length			// 配列overﾁｪｯｸ
							? rowsMap.getOrDefault( bodyTypes[row],rowStr )	// 存在しなかった場合の処置
							: rowStr ;

			return new TagParser() {
				/**
				 * 開始ﾀｸﾞから終了ﾀｸﾞまでの文字列の処理を定義します｡
				 *
				 * @param str 開始ﾀｸﾞから終了ﾀｸﾞまでの文字列(開始ﾀｸﾞ･終了ﾀｸﾞを含む)
				 * @param buf 出力を行う文字列ﾊﾞｯﾌｧ
				 * @param offset 終了ﾀｸﾞのｵﾌｾｯﾄ(ここでは使っていません)
				 */
				@Override
				protected void exec( final String str, final StringBuilder buf, final int offset ) {
					final String key = TagParser.checkKey( str, buf );
					if( key.indexOf( '<' ) >= 0 ){
						final String errMsg = "[ERROR]SHEET:{@と}の整合性が不正です｡" + CR
											+ "変数内の特定の文字列に書式設定がされている可能性があります｡ｷｰ=" + key;
						throw new HybsSystemException( errMsg );
					}
					final SplitKey spKey = new SplitKey( key );		// 8.0.3.0 (2021/12/17)
					buf.append( VAR_START ).append( spKey.incrementKey( idx-offsetCnt ) ).append( VAR_END );
				}
			}.doParse( dummy, VAR_START, VAR_END, false );
		}
		return rowStr;
	}

	/**
	 * ｼｰﾄに含まれている行数を返します｡
	 *
	 * @og.rev 8.0.3.0 (2021/12/17) 新規追加
	 *
	 * @return ｼｰﾄに含まれている行数
	 */
	public int getRowCnt() {
		return sheetRows.size();
	}
}
