/*
 * 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 org.opengion.hayabusa.common.HybsSystemException;

/**
 * シート単位のcontent.xmlを管理するためのクラスです。
 * シートのヘッダー、行の配列、フッター及びシート名を管理します。
 *
 * 7.0.1.5 (2018/12/10)
 *   LINECOPYでは、同一行をコピーするため、セルの選択行(A5とか$C7とか)までコピーされ
 *   行レベルの計算が出来ません。その場合は、INDIRECT(ADDRESS(ROW(),列番号))関数を
 *   使用することでセルのアドレスが指定可能です。
 *   列番号は、A=1 です。
 *
 * @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 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 LINE_COPY = "LINECOPY";

	private final List<String>	sheetRows	= new ArrayList<>();
	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) シート名定義対応
	 *
	 * @param sheet シート名
	 * @param bodyRowCount 行番号
	 */
	public void analyze( final String sheet, final int bodyRowCount ) {
		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)
		}

		sheetName = TagParser.getValueFromTag( sheetHeader, SHEET_NAME_START, SHEET_NAME_END );
		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;LINE_COPY}が存在した場合に、テーブルモデル分だけ
	 * 行をコピーします。
	 * その際、{&#064;xxx_y}のyをカウントアップしてコピーします。
	 *
	 * 整合性等のエラーハンドリングはこのメソッドでは行わず、
	 * 実際のパース処理中で行います。
	 *
	 * 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のみ追記)
	 *
	 * @param row		行データ
	 * @param rowCount	カウンタ
	 */
	private void lineCopy( final String row, final int rowCount ) {
		// この段階で存在しなければ即終了
		final int lcStrOffset = row.indexOf( VAR_START + LINE_COPY );
		if( lcStrOffset < 0 ) { return; }
		final int lcEndOffset = row.indexOf( VAR_END, lcStrOffset );
		if( lcEndOffset < 0 ) { return; }

		final StringBuilder lcStrBuf = new StringBuilder( row );
		final String lcKey = TagParser.checkKey( row.substring( lcStrOffset + VAR_START.length(), lcEndOffset ), lcStrBuf );
		if( lcKey == null || !LINE_COPY.equals( lcKey ) ) { return; }

		// 存在すればテーブルモデル行数-1回ループ(自身を除くため)
		for( int i=1; i<rowCount; i++ ) {
			final int cRow = i;
			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]PARSE:{@と}の整合性が不正です。変数内の特定の文字列に書式設定がされている可能性があります。キー=" + key;
						throw new HybsSystemException( errMsg );
					}
					buf.append( VAR_START ).append( incrementKey( key, cRow ) ).append( VAR_END );
				}
			}.doParse( lcStrBuf.toString(), VAR_START, VAR_END, false );
			sheetRows.add( rowStr );
		}
	}

	/**
	 * xxx_yのy部分を引数分追加して返します。
	 * yが数字でない場合や、_が無い場合はそのまま返します。
	 *
	 * @og.rev 5.0.0.2 LINE_COPYで利用するために追加
	 *
	 * @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) あらかじめ、必要な配列の長さを確保しておきます。
	 *
	 * @return シートの各行の配列
	 * @og.rtnNotNull
	 */
	public String[] getRows() {
		return sheetRows.toArray( new String[sheetRows.size()] );
	}
}
