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

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.XHTMLTag;
import org.opengion.hayabusa.html.AbstractViewForm;
import org.opengion.hayabusa.html.ViewCalendarParam;
import static org.opengion.fukurou.util.HybsConst.BR;	// 6.1.0.0 (2014/12/26) refactoring

import java.util.Calendar;

/**
 * １ヶ月分のカレンダー形式で、検索結果を表示する、カレンダー表示クラスです。
 *
 * AbstractViewForm により、setter/getterメソッドのデフォルト実装を提供しています。
 * 各HTMLのタグに必要な setter/getterメソッドのみ，追加定義しています。
 *
 * AbstractViewForm を継承している為,ロケールに応じたラベルを出力させる事が出来ます。
 *
 * @og.group 画面表示
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class ViewForm_HTMLCalendar extends AbstractViewForm {
	//* このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "5.1.6.0 (2010/05/01)" ;

	private static final String[] WEEK_ja = { "日" ,"月" ,"火" ,"水" ,"木" ,"金" ,"土" ,"日" ,"月" };
	private static final String[] WEEK_en = { "SUN","MON","TUE","WED","THU","FRI","SAT","SUN","MON" };
	private static final String[] WEEK_MK = { "SUN","",   "",   "",   "",   "",   "SAT","SUN",""    };

	private String[] week		   ;
	private String[] viewKeys	   ;		// カレンダを指定するキー配列	// 3.6.0.0 (2004/09/17)
	private String	 ymKey		   ;		// 年月のカラム名
	private String	 dayKey		   ;		// 休日(0:通常、1:休日)のカラム名
	private String	 valKey		   ;		// カレンダに表示する値のカラム名
	private boolean	 valueBrFlag   ;		// 日付けと値の間に、BRタグを入れるかどうか
	private int		 firstWeek	   ;		// 曜日の開始(0:日曜 1:月曜)
	private int		 columnSize	   ;		// カレンダの横方向の繰り返し数	// 3.6.0.0 (2004/09/17)

	/**
	 * DBTableModel から HTML文字列を作成して返します。
	 * startNo(表示開始位置)から、pageSize(表示件数)までのView文字列を作成します。
	 * 表示残りデータが pageSize 以下の場合は,残りのデータをすべて出力します。
	 *
	 * @og.rev 3.5.2.1 (2003/10/27) 属性値の指定のシングルクオートをダブルクオートに変更。
	 * @og.rev 3.6.0.0 (2004/09/17) 複数カレンダに対応
	 * @og.rev 3.6.0.5 (2004/10/18) calenderParam の viewKeys バグ対応。
	 * @og.rev 3.7.0.1 (2005/01/31) 全件チェックコントロール処理変更
	 *
	 * @param  startNo    表示開始位置
	 * @param  pageSize   表示件数
	 *
	 * @return  DBTableModelから作成された HTML文字列
	 * @og.rtnNotNull
	 */
	public String create( final int startNo, final int pageSize )  {
		if( getRowCount() == 0 ) { return ""; }	// 暫定処置

		paramInit();

		final int lastNo = getLastNo( startNo, pageSize );

		final StringBuilder out = new StringBuilder( BUFFER_LARGE )
			.append( "<table><tr>" ).append( CR );

		final boolean onlyOne = ( lastNo - startNo ) == 1 ;	// 互換性のため、１件の処理のみ変更

		// 3.7.0.1 (2005/01/31) 全件チェックコントロール処理変更
		if( ! onlyOne && isUseCheckControl() && "checkbox".equals( getSelectedType() ) ) {
			out.append( "<tr><td>" ).append( getAllCheckControl() ).append( "</td></tr>");
		}

		for( int row=startNo; row<lastNo; row++ ) {
			out.append( "<td valign=\"top\">" );

			if( isWritable( row ) ) {
				if( onlyOne ) {
					out.append( XHTMLTag.hidden( HybsSystem.ROW_SEL_KEY,String.valueOf( row ) ) )
						.append( CR );
				}
				else {
					out.append( "<input type=\"" ).append( getSelectedType() )
						.append( "\" name=\"" ).append( HybsSystem.ROW_SEL_KEY ).append( '"' );
					if( isChecked( row ) ) { out.append( " checked=\"checked\"" ); }
					out.append( " value=\"" ).append( row ).append( "\" />" );
				}
			}

			// 3.6.0.5 (2004/10/18)
			for( int col = 0; col < viewKeys.length; col++ ) {
				if( "_".equals( viewKeys[col] ) ) { continue; }
				final int newCol = getColumnNo( viewKeys[col] );
				out.append("<span id=\"label\">")
					.append( getColumnLabel( newCol ) )
					.append( ":</span>" )
					.append( getValueLabel(row,newCol) )
					.append( "&nbsp;" );
			}
			out.append( BR )
				.append( makeCalendarData( row ) )
				.append( "</td>" );
			if( (row+1) % columnSize == 0 ) {
				out.append( "</tr><tr>" ).append( CR );
			}
		}
		out.append( "</tr></table>" ).append( CR );

		return out.toString();
	}

	/**
	 * DBTableModel の指定の行番号より、カレンダのHTML文字列を作成して返します。
	 *
	 * @og.rev 3.6.0.0 (2004/09/17) create( int startNo, int pageSize ) のロジックを移動
	 * @og.rev 3.6.0.5 (2004/10/18) 印刷時の罫線出力関連機能の追加。id 属性を出力します。
	 *
	 * @param  row    指定の行番号
	 *
	 * @return  指定の行番号のカレンダのHTML文字列
	 * @og.rtnNotNull
	 */
	private String makeCalendarData( final int row ) {
		final String yyyymm = getValue( row,getColumnNo( ymKey ));
		final Calendar currentCal = getCalendar( yyyymm );

		final StringBuilder out = new StringBuilder( BUFFER_LARGE )
			.append( "<table id=\"viewCalendar\" border=\"0\" cellpadding=\"1\" cellspacing=\"2\">" )
			.append( CR )										// 3.6.0.5 (2004/10/18)
			.append( " <tr><td class=\"titleStyle\" colspan=\"7\">" )
			.append( getValueLabel( row,getColumnNo( ymKey )) )
			.append( "</td></tr>" ).append( CR );

		// 今月の最終日
		final int daysInMonth = currentCal.getActualMaximum(Calendar.DAY_OF_MONTH);
		// カレンダーの週の初めを設定する。月曜開始のカレンダーを作成するときに利用
		currentCal.setFirstDayOfWeek( Calendar.SUNDAY + firstWeek );

		// 週を表す番号  ０～６ の間
		int culDay = ( currentCal.get(Calendar.DAY_OF_WEEK)
					 	+ ( 7 - currentCal.getFirstDayOfWeek() ) ) % 7 ;

		// 曜日欄を記述します。
		out.append(" <tr>").append( CR );
		for( int i=0; i<7; i++ ) {
			out.append("  <td width=\"14%\" class=\"dayHead" )
				.append( WEEK_MK[i+firstWeek] ).append( "\">" )
				.append( week[i+firstWeek] ).append( "</td>" )
				.append( CR );
		}
		out.append(" </tr>").append( CR )
			.append(" <tr>").append( CR );				// 6.1.0.0 (2014/12/26) refactoring

		// 第一週の日付欄の空きスペースの計算
		if( culDay != 0 ) {
			out.append("  <td colspan=\"" ).append( culDay ).append( "\">&nbsp;</td>" ).append( CR );
		}

		// 日付欄を埋めます。
		final String BR = valueBrFlag ? "<br />" : "" ;
		for(int day=1; day <= daysInMonth; day++) {
			final int    daycol = getColumnNo( dayKey + day );	// 動的日付けカラム番号取得
			final String daylbl = getValueLabel( row,daycol,day );	// ローカルの日付けラベルを求めるメソッド
			String vallbl = "" ;
			if( valKey != null ) {
				final int valcol = getColumnNo( valKey + day );	// 動的値カラム番号取得
				vallbl = BR + getValueLabel( row,valcol );		// 通常の値をラベルを求めるメソッド
			}

			if( "1".equals( getValue( row,daycol ))) {
				out.append("  <td class=\"holyday\">" )
					.append( daylbl )
					.append( vallbl )
					.append("</td>");
			} else {
				out.append("  <td class=\"day" )
					.append( WEEK_MK[culDay+firstWeek] )
					.append("\">")
					.append( daylbl )
					.append( vallbl )
					.append("</td>");
			}
			out.append( CR );

			// 週の終わりで、行を折り返す。
			if( culDay == 6 ) {
//				out.append("  </tr>\n  <tr>").append( CR );
				out.append(" </tr>" ).append( CR )
					.append( " <tr>").append( CR );			// 6.1.0.0 (2014/12/26) refactoring
				culDay = 0;
			} else {
				culDay++;
			}
		}

		// 最終週の日付欄の空きスペースの計算
		if( (7-culDay) != 0 ) {
			out.append( "  <td colspan=\"" ).append( 7-culDay ).append( "\">&nbsp;</td>" );
		}
		out.append( CR )
			.append( " </tr>" ).append( CR )
			.append( "</table>" ).append( CR );

		return out.toString();
	}

	/**
	 * このビーに対する特別な初期化を行う。
	 *
	 * @og.rev 3.5.4.0 (2003/11/25) TableFormatter クラスを使用するように変更。
	 * @og.rev 3.5.5.9 (2004/06/07) ヘッダーの日付け表示に、Locale を加味できるように変更
	 * @og.rev 3.6.0.0 (2004/09/17) viewKeys , columnSize 属性の追加
	 *
	 */
	private void paramInit() {

		final String viewKeysCd	= getParam( ViewCalendarParam.VIEW_KEYS			,ViewCalendarParam.VIEW_VALUES			);	// 3.6.0.0 (2004/09/17)
		ymKey				= getParam( ViewCalendarParam.YM_KEY			,ViewCalendarParam.YM_VALUE				);
		dayKey				= getParam( ViewCalendarParam.DAY_KEY			,ViewCalendarParam.DAY_VALUE			);
		valKey				= getParam( ViewCalendarParam.VALUE_KEY			,null									);
		final String valueBrCd	= getParam( ViewCalendarParam.VALUE_BR_FLAG_KEY	,ViewCalendarParam.VALUE_BR_FLAG_VALUE	);
		final String firstWeekCd	= getParam( ViewCalendarParam.FIRSTWEEK_KEY		,ViewCalendarParam.FIRSTWEEK_VALUE		);
		final String headerLocale	= getParam( ViewCalendarParam.HEADER_LOCALE_KEY	,ViewCalendarParam.HEADER_LOCALE_VALUE	);
		final String columnSizeCd	= getParam( ViewCalendarParam.COLUMN_SIZE_KEY	,ViewCalendarParam.COLUMN_SIZE_VALUE	);	// 3.6.0.0 (2004/09/17)

		viewKeys    = StringUtil.csv2Array( viewKeysCd );
		firstWeek   = Integer.parseInt( firstWeekCd );
//		valueBrFlag = Boolean.valueOf( valueBrCd ).booleanValue() ;
		valueBrFlag = Boolean.parseBoolean( valueBrCd );		// 6.1.0.0 (2014/12/26) refactoring
		columnSize  = Integer.parseInt( columnSizeCd );			// 3.6.0.0 (2004/09/17)

		// 曜日ヘッダーをコピーして作成します。
		if( "ja".equals( headerLocale ) ) {
			week = WEEK_ja;
		}
		else {
			week = WEEK_en;
		}
	}

	/**
	 * row行，colum列 のデータの値をHTML文字列に変換して返します。
	 * リソースバンドルが登録されている場合は,リソースに応じた
	 * HTML文字列を作成します。
	 * カレンダーViewに特化した、ラベル値を返します。
	 *
	 * @og.rev 3.8.0.9 (2005/10/17) writableControl 追加による引数変更
	 *
	 * @param	row		行番号
	 * @param	column	カラム番号
	 * @param	day		日付
	 *
	 * @return	row行，colum列 のデータの値
	 * @og.rtnNotNull
	 */
	private String getValueLabel( final int row, final int column, final int day ) {
		if( isColumnWritable( column ) && isWritable( row ) ) {
			final String val = getValue( row,column ) ;
			return day + getEditorValue( row,column,val );
		}
		else {
			return String.valueOf( day );
		}
	}

	/**
	 * 指定のYYYYMM文字列から、カレンダーオブジェクトを作成して返します。
	 * 日にちは、１日にセットされます。
	 *
	 * @param   ym YYYYMM文字列
	 *
	 * @return  カレンダーオブジェクト
	 */
	private Calendar getCalendar( final String ym ) {
		final Calendar cal = Calendar.getInstance();

		if( ym != null && ym.length() == 6 ) {
			final int yyyy = Integer.parseInt( ym.substring( 0,4 ) );
			final int mm   = Integer.parseInt( ym.substring( 4,6 ) ) - 1;
			final int dd   = 1;
			cal.set( yyyy,mm,dd,0,0,0 );
		}
		else {
			// カレンダーを今月の１日に設定する。
			cal.set(Calendar.DAY_OF_MONTH, 1);
		}

		return cal ;
	}

	/**
	 * フォーマットメソッドを使用できるかどうかを問い合わせます。
	 *
	 * @og.rev 3.6.0.0 (2004/09/17) 親クラス変更に伴なう、追加
	 *
	 * @return  使用可能(true)/ 使用不可能 (false)
	 */
	public boolean canUseFormat() {
		return false;
	}

	/**
	 * 表示項目の編集(並び替え)が可能かどうかを返します。
	 *
	 * @og.rev 5.1.6.0 (2010/05/01) 新規追加
	 *
	 * @return	表示項目の編集(並び替え)が可能かどうか(false:不可能)
	 */
	@Override
	public boolean isEditable() {
		return false;
	}
}
