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

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.db.DBTableModel;
import org.opengion.hayabusa.develop.JspCreateFactory;
import org.opengion.hayabusa.develop.JspConvertEntity;
import org.opengion.fukurou.util.ErrorMessage;
import org.opengion.fukurou.util.StringUtil;
import static org.opengion.fukurou.util.StringUtil.nval ;
import org.opengion.fukurou.xml.JspSaxParser;

import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.io.File;

import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;

/**
 * JspCreateTag は、画面定義情報より、JSP画面を自動作成するツールです。
 *
 * 画面作成するにあたり、３箇所の参照元が存在します。
 *
 *   １．画面属性の設定 ： 雛形   → 仮画面   ・・・ hpgid で指定の画面ID
 *   ２．仮画面修正     ： 仮画面 → 仮画面   ・・・ jsp/customUserDef/画面ID
 *   ３．本環境修正     ： 本画面 → 本画面   ・・・ jsp/画面ID
 *
 * それぞれ、作成先の画面IDフォルダが存在する場合は、取込元を作成先に設定します。
 * つまり、一度作成すると、それ以降は、作成された画面を基準に処理を行います。
 *
 * @og.formSample
 * ●形式：&lt;og:jspCreate outdir="…" pgid="…" /&gt;
 * ●body：なし
 *
 * ●Tag定義：
 *   &lt;og:jspCreate
 *       pgid             ○【TAG】プログラムIDを指定します。(必須)
 *       outdir             【TAG】作成先のディレクトリを指定します
 *       hpgid              【TAG】雛形のプログラムIDを指定します
 *       useUserDef         【TAG】仮環境を使用するかどうか[true/false]を指定します(初期値:true:使用する)
 *       tableId            【TAG】(通常は使いません)DBTableModel が登録されているメモリのキーを指定します
 *       scope              【TAG】キャッシュする場合のスコープ[request/page/session/applicaton]を指定します(初期値:session)
 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
 *   /&gt;
 *
 * ●使用例
 *    ・先に、query タグでDBTableModel が作成済み
 *          &lt;og:jspCreate
 *              outdir      = "{&#64;SYS.REAL_PATH}jsp/"    ：出力先フォルダ
 *              pgid        = "{&#64;PGID}"                 ：作成画面ID
 *              hpgid       = "{&#64;HPGID}"                ：雛形画面ID
 *              useUserDef  = "true/false"                  ：仮環境の使用可否(初期値:true 使用する)
 *          /&gt;
 *
 * @og.group 開発補助
 * @og.rev 5.1.9.0 (2010/08/01) 新規作成
 *
 * @version  0.9.0  2000/10/17
 * @author   Kazuhiko Hasegawa
 * @since    JDK1.6,
 */
public class JspCreateTag extends CommonTagSupport {
	//* このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "5.1.9.0 (2010/08/01)" ;

	private static final long serialVersionUID = 5190 ;	// 5.1.9.0 (2010/08/01)

//	private static final String ENCODE = "UTF-8" ;

//	private static String JSP_DIR = "C:/opengion/uap/webapps/gf/jsp/";
	private static String JSP_DIR = HybsSystem.sys( "REAL_PATH" ) + "jsp/" ;

	private static final String CUST_DEF = "customUserDef/" ;

	private transient DBTableModel	table	= null;

	private String	tableId		= HybsSystem.TBL_MDL_KEY;
	private String	outdir		= JSP_DIR;						// 出力先フォルダ
	private String	pgid		= null;							// 作成画面ID
	private String	hpgid		= null;							// 雛形画面ID
	private boolean	useUserDef	= true;							// 仮環境の使用可否(初期値:true 使用する)

	/**
	 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
	 *
	 * @return	後続処理の指示(SKIP_BODY)
	 */
	@Override
	public int doStartTag() {
		table = (DBTableModel)getObject( tableId );

		return(SKIP_BODY);				// Body を評価しない
//		return ( EVAL_BODY_BUFFERED );	// Body を評価する
	}

	/**
	 * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
	 *
	 * @return	int
	 */
//	@Override
//	public int doAfterBody() {
//		body = nval( getBodyString(),body );
//
//		return(SKIP_BODY);
//	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doEndTag() {
		// デバッグ時には、オブジェクト内部情報を表示する。
		if( isDebug() ) {
			debugPrint();
		}

		boolean okFlag = execute();

		if( okFlag )  { 	// 正常
			return(EVAL_PAGE);
		}
		else {
			return(SKIP_PAGE);
		}
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 */
	@Override
	protected void release2() {
		super.release2();
		table		= null;
		tableId		= HybsSystem.TBL_MDL_KEY;
		outdir		= JSP_DIR;
		pgid		= null;
		hpgid		= null;
		useUserDef	= true;
	}

	/**
	 * DBTableModel処理を実行します。
	 *
	 * @og.rev 5.2.1.0 (2010/10/01) 実行クラスのクラス名を変更します。 _OG_ を削除
	 * @og.rev 5.5.2.6 (2012/05/25) findbugs対応。JspConvertEntity.DBKEY を、JspConvertEntity.getDBKEY() に変更。
	 *
	 * @return	処理の実行結果
	 */
	public boolean execute() {
		boolean okFlag = false;

		// 出力先(セーブ先)のフォルダ
		File saveDir = (useUserDef) ? new File( outdir + CUST_DEF , pgid ) : new File( outdir , pgid ) ;

		// 雛形(参照元)のフォルダ
		final File refDir ;
		if( saveDir.exists() ) {		// 出力先フォルダが存在した場合
			refDir = saveDir ;			// 出力先フォルダが、雛形フォルダになる。
		}
		else {							// 出力先フォルダが存在しない場合
			refDir = new File( outdir , hpgid );			// 雛形画面IDをそのまま使用します。
			if( ! saveDir.mkdirs() ) {
				String errMsg = "出力先のフォルダが作成できませんでした。[" + saveDir + "]" ;
				throw new HybsSystemException( errMsg );
			}
		}

		if( ! refDir.exists() ) {	// 雛形(参照元)フォルダが存在しない場合、エラー
			String errMsg = "雛形(参照元)フォルダが存在しません。[" + refDir + "]" ;
			throw new HybsSystemException( errMsg );
		}

//		PrintWriter writer = null;

//		int[] clmNo = getTableColumnNo( JspConvertEntity.DBKEY );
		int[] clmNo = getTableColumnNo( JspConvertEntity.getDBKEY() );		// 5.5.2.6 (2012/05/25) findbugs対応
		int rowCnt  = table.getRowCount();

		int row = 0;
		boolean unCmntSet = true;
		String[] data = null;
		try {
			//JSPを書く為に必要な情報を設定
			Map<String,List<JspConvertEntity>> tables = new HashMap<String,List<JspConvertEntity>>();

			for( row=0; row<rowCnt; row++ ) {
				data = table.getValues( row );
				JspConvertEntity entry = JspConvertEntity.newInstance( data, clmNo );

				if( entry != null ) {
					List<JspConvertEntity> array = tables.get( entry.getType() );
					if( array == null ) { array = new ArrayList<JspConvertEntity>(); }
					array.add( entry );
					tables.put( entry.getType(),array );
					if( unCmntSet ) {
						tables.put( "COMMENT",array );
						unCmntSet = false;
					}
				}
			}

			// 参照先フォルダから、出力先フォルダに、XMLコピー処理を行います。
			JspSaxParser spar = new JspSaxParser();

			// 5.2.1.0 (2010/10/01) 実行クラスのクラス名を変更します。 _OG_ を削除
			spar.addFilter( JspCreateFactory.newInstance( "COMMENT"	,tables) );
			spar.addFilter( JspCreateFactory.newInstance( "COLUMN"	,tables) );
			spar.addFilter( JspCreateFactory.newInstance( "SELECT"	,tables) );
			spar.addFilter( JspCreateFactory.newInstance( "QUERY"	,tables) );
			spar.addFilter( JspCreateFactory.newInstance( "VIEW"	,tables) );

			spar.copyDirectry( refDir,saveDir );

//			//query.jsp作成
//			writer = FileUtil.getPrintWriter( new File( saveDir,"query.jsp" ),ENCODE );
//			writer.print(writeQueryJSP( refDir , tables  ));
//			writer.println();
//			writer.flush();
//
//			//result.jsp作成
//			writer = FileUtil.getPrintWriter( new File( saveDir,"result.jsp" ),ENCODE );
//			writer.print(writeResultJSP( refDir , tables  ));
//			writer.println();
//			writer.flush();

			okFlag = true;
		}
		catch( RuntimeException ex ) {
			ex.printStackTrace();
			ErrorMessage errMessage = new ErrorMessage( "JspCreateTag Error" );
			errMessage.addMessage( row+1,ErrorMessage.NG,"JSPOUT",ex.getMessage() );
			errMessage.addMessage( row+1,ErrorMessage.NG,"JSPOUT",StringUtil.array2csv( data ) );
			errMessage.addMessage( row+1,ErrorMessage.NG,"JSPOUT","PRGID=[" + pgid + "]" );
			// BAT から呼び出す場合があるため、標準エラー出力にも情報を出しておきます。
			System.out.println( errMessage );
		}
//		finally {
//			Closer.ioClose( writer );
//		}

		return okFlag;
	}

	/**
	 * query.jspファイルの内容を生成します。
	 *
	 * @param refDir
	 * @param tables
	 *
	 * @return 生成した内容
	 */
//	private String writeQueryJSP( File refDir , HashMap<String , ArrayList<JspConvertEntity>> tables ) {
//		String jsp = "";
//		try{
//			//利用する生成クラスを設定
//			HashMap< String , JspCreate > generates = new HashMap< String , JspCreate >();
//			generates.put( "og:comment"	, JspCreateFactory.newInstance( "OG_COMMENT",tables) );
//			generates.put( "table" 		, JspCreateFactory.newInstance( "OG_COLUMN",tables) );
//			generates.put( "og:select"	, JspCreateFactory.newInstance( "OG_SELECT",tables) );
//
//			//JSPの作成処理を実施。
//			JspHandler handler = new JspHandler();
//			jsp = handler.generate( new File( refDir , "query.jsp" ), ENCODE , generates);
//
//		}catch(Throwable ex){
//			ex.printStackTrace();
//			throw new RuntimeException(ex);
//		}
//		return jsp;
//	}

	/**
	 * result.jspファイルの内容を生成します。
	 *
	 * @param refDir
	 * @param tables
	 *
	 * @return 生成した内容
	 */
//	private String writeResultJSP(File refDir , HashMap<String , ArrayList<JspConvertEntity>> tables ) {
//		String jsp = "";
//		try{
//			//利用する生成クラスを設定
//			HashMap< String , JspCreate > generates = new HashMap< String , JspCreate >();
//			generates.put( "og:comment"	, JspCreateFactory.newInstance( "OG_COMMENT",tables) );
//			generates.put( "og:query"	, JspCreateFactory.newInstance( "OG_QUERY",tables ) );
//			generates.put( "og:view"	, JspCreateFactory.newInstance( "OG_VIEW",tables) );
//
//			//JSPの作成処理を実施。
//			JspHandler handler = new JspHandler();
//			jsp = handler.generate( new File( refDir , "result.jsp" ) , ENCODE  , generates);
//
//		}catch(Throwable ex){
//			ex.printStackTrace();
//			throw new RuntimeException(ex);
//		}
//		return jsp;
//	}

	/**
	 * 【TAG】(通常は使いません)DBTableModel が登録されているメモリのキーを指定します。
	 *
	 * @og.tag
	 * 初期値は、HybsSystem.TBL_MDL_KEY です。
	 * この設定値から、DBTableModel を取り出します。リクエストかセッションかは、
	 * scope 属性で指定します。
	 *
	 * @param	id メモリから取り出す時の ID
	 */
	public void setTableId( final String id ) {
		tableId = nval( getRequestParameter( id ),tableId );
	}

	/**
	 * 【TAG】作成先のディレクトリを指定します。
	 *
	 * @og.tag
	 * 作成先のディレクトリを指定します。
	 * 初期値は、実際に実行しているアプリケーションの REAL_PATH + jsp フォルダ以下です。
	 * 作成先のフォルダも、useUserDef の設定によって異なります。
	 *
	 * @param	dir	出力先のディレクトリ
	 */
	public void setOutdir( final String dir ) {
		outdir = nval( getRequestParameter( dir ),outdir );
	}

	/**
	 * 【TAG】プログラムIDを指定します。
	 *
	 * @og.tag
	 * 作成先のプログラムIDを指定します。
	 * ただし、作成先の実際のフォルダは、useUserDef の設定によって異なります。
	 *
	 * @param	id	プログラムID
	 */
	public void setPgid( final String id ) {
		pgid = nval( getRequestParameter( id ),pgid );
	}

	/**
	 * 【TAG】雛形のプログラムIDを指定します。
	 *
	 * @og.tag
	 * 雛形のプログラムIDをパースして、実際のプログラムを作成します。
	 * ただし、作成先の実際のフォルダは、useUserDef の設定によって異なります。
	 * また、パースするのは、作成先の画面IDのフォルダが存在しない場合のみです。
	 * すでに、存在している場合は、元の画面IDのフォルダを読み取って、パースを
	 * 行います。基本的に、作成先のソースを手で修正した場合でも、パースと
	 * 無関係な箇所の修正はそのまま反映のこされます。
	 *
	 * @param	id	雛形のプログラムID
	 */
	public void setHpgid( final String id ) {
		hpgid = nval( getRequestParameter( id ),hpgid );
	}

	/**
	 * 【TAG】仮環境を使用するかどうか[true/false]を指定します(初期値:true:使用する)。
	 *
	 * @og.tag
	 * true:使用する を設定すると、"customUserDef" フォルダの下に、画面IDの
	 * フォルダを作成します。
	 * false:使用しない を設定すると、実際の リアルパス(REAL_PATH/jsp)の下に、
	 * 画面IDのフォルダを作成します。こちらは、実際の画面と同様に、画面リソース等を
	 * 作成してアクセスすることになります。
	 *
	 * @param	flag	仮環境を使用するかどうか [true:使用する/false:使用しない]
	 */
	public void setUseUserDef( final String flag ) {
		useUserDef = nval( getRequestParameter( flag ),useUserDef );
	}

	/**
	 *  カラム名配列(String[])より、対応するカラムNo配列(int[])を作成します。
	 *
	 * @param	nameArray カラム名配列
	 *
	 * @return	カラムNo配列
	 */
	private int[] getTableColumnNo( final String[] nameArray ) {
		int[] clmNo = new int[ nameArray.length ];
		for( int i=0; i<clmNo.length; i++ ) {
			clmNo[i] = table.getColumnNo( nameArray[i] );
		}
		return clmNo;
	}

	/**
	 * シリアライズ用のカスタムシリアライズ書き込みメソッド
	 *
	 * @param	strm	ObjectOutputStreamオブジェクト
	 * @serialData
	 */
	private void writeObject( final ObjectOutputStream strm ) throws IOException {
		strm.defaultWriteObject();
	}

	/**
	 * シリアライズ用のカスタムシリアライズ読み込みメソッド
	 *
	 * ここでは、transient 宣言された内部変数の内、初期化が必要なフィールドのみ設定します。
	 *
	 * @param	strm	ObjectInputStreamオブジェクト
	 * @see #release2()
	 * @serialData
	 */
	private void readObject( final ObjectInputStream strm ) throws IOException , ClassNotFoundException {
		strm.defaultReadObject();
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 */
	@Override
	public String toString() {
		return org.opengion.fukurou.util.ToString.title( this.getClass().getName() )
				.println( "VERSION"			,VERSION		)
				.println( "tableId"			,tableId		)
				.println( "outdir"			,outdir			)
				.println( "pgid"			,pgid			)
				.println( "hpgid"			,hpgid			)
				.println( "useUserDef"		,useUserDef		)
				.fixForm().toString() ;
	}
}
