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

import org.opengion.fukurou.db.ConnectionFactory;
import org.opengion.fukurou.db.DBFunctionName;
import org.opengion.fukurou.db.DBUtil;
import org.opengion.fukurou.mail.MailTX;
import org.opengion.fukurou.util.ApplicationInfo;
import org.opengion.fukurou.util.LogWriter;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.db.DBTableModel;
import org.opengion.hayabusa.db.DBTableModelUtil;
import org.opengion.hayabusa.resource.ResourceFactory;
import org.opengion.hayabusa.resource.ResourceManager;

/**
 * DBからキューを作成するためのクラスです。
 * キューはGE5xテーブルから作成されます。
 *
 * キュー生成時点(処理スレッドにスタックした時点)では、帳票データのテーブルモデルは作成されません。
 * 帳票データは、各スレッドからset()メソッドを呼び出したタイミングで生成されます。
 *
 * 処理開始及び、完了のステータスは、GE50の完成フラグに更新されます。
 * また、エラー発生時のメッセージは、GE56に更新されます。
 *
 * @og.group 帳票システム
 *
 * @version  4.0
 * @author   Hiroki.Nakamura
 * @since    JDK1.6
 */
public final class QueueManager_DB implements QueueManager {
	
	/** コネクションにアプリケーション情報を追記するかどうか指定 */
	private static final boolean USE_DB_APPLICATION_INFO  = HybsSystem.sysBool( "USE_DB_APPLICATION_INFO" ) ;

	/** アプリケーション情報 */
	private static final ApplicationInfo appInfo;
	static {
		if( USE_DB_APPLICATION_INFO ) {
			appInfo = new ApplicationInfo();
			// ユーザーID,IPアドレス,ホスト名
			appInfo.setClientInfo( "ReportDaemon", HybsSystem.HOST_ADRS, HybsSystem.HOST_NAME );
			// 画面ID,操作,プログラムID
			appInfo.setModuleInfo( "ReportDaemon", "QueueManager", "QueueManager" );
		}
		else {
			appInfo = null;
		}
	}
	
//	private static final String SQL_SELECT_GE50 =
//		"select a.system_Id, a.ykno, a.listid, a.outdir, a.outfile, a.pdf_passwd"
//		+ ", b.lang, b.fgrun, b.dmn_grp "
//		+ ", c.modeldir, c.modelfile, d.prtnm, c.fglocal, c.fgcut, c.bsql, c.hsql, c.fsql "
//		+ "from ge50 a, ge53 b, ge54 c, ge55 d "
//		+ "where a.fgkan='1' "
//		+ "and a.system_id = b.system_id and a.joken = b.joken "
//		+ "and a.system_id = c.system_id and a.listid = c.listid "
//		+ "and b.system_id = d.system_id and b.prtid = d.prtid order by"
//		+ HybsSystem.sys( "REPORT_DAEMON_ORDER_BY" );
	
	// 4.3.0.0 (2008/07/18) PDF出力時に出力先マスタは不要のためGE55は外部結合に変更
//	private static final String SQL_SELECT_GE50 =
//		"select a.system_Id, a.ykno, a.listid, a.outdir, a.outfile, a.pdf_passwd"
//		+ ", b.lang, b.fgrun, b.dmn_grp "
//		+ ", c.modeldir, c.modelfile, d.prtnm, c.fglocal, c.fgcut, c.bsql, c.hsql, c.fsql "
//		+ "from ge50 a, ge53 b, ge54 c, ge55 d "
//		+ "where a.fgkan='1' "
//		+ "and a.system_id = b.system_id and a.joken = b.joken "
//		+ "and a.system_id = c.system_id and a.listid = c.listid "
//		+ "and b.system_id = d.system_id(+) and b.prtid = d.prtid(+) order by"
//		+ HybsSystem.sys( "REPORT_DAEMON_ORDER_BY" );

	// 4.3.7.0 (2009/06/01) HSQLDB対応
	private static final String CON = DBFunctionName.getFunctionName( "CON", ConnectionFactory.getDBFullName( null ) );

	// 4.3.3.6 (2008/11/15) マルチサーバ対応追加（GE12から処理対象デーモングループ取得）
	// 4.3.7.0 (2009/06/01) HSQLDB対応
	private static final String SQL_SELECT_GE50 =
		"select a.system_Id, a.ykno, a.listid, a.outdir, a.outfile, a.pdf_passwd"
		+ ", b.lang, b.fgrun, b.dmn_grp "
		+ ", c.modeldir, c.modelfile, d.prtnm, c.fglocal, c.fgcut, c.bsql, c.hsql, c.fsql "
//		+ "from ge50 a, ge53 b, ge54 c, ge55 d "
		+ "from ge50 a "
		+ "inner join ge53 b "
		+ "on a.system_id = b.system_id and a.joken = b.joken "
		+ "inner join ge54 c "
		+ "on a.system_id = c.system_id and a.listid = c.listid " 
		+ "left outer join ge55 d "
		+ "on b.system_id = d.system_id and b.prtid = d.prtid "
		+ "where a.fgkan='1' "
		+ "and exists ( select 'X' from ge12 e "
		+				"where	e.fgj				='1' "
		+				"and		e.system_id 	= '"
		+				HybsSystem.sys( "SYSTEM_ID" )
		+				"' "
		+				"and		e.contxt_path	= '"
		+				HybsSystem.sys( "HOST_URL" )
		+				"' "
		+				"and		e.param_id		like 'REPORT2_HANDLE_DAEMON_%' "
		+				"and		e.param			= 'RUN_'" + CON + "a.system_id" + CON + "'_'" + CON + "b.dmn_grp"
		+			") "
//		+ "and a.system_id = b.system_id and a.joken = b.joken "
//		+ "and a.system_id = c.system_id and a.listid = c.listid "
//		+ "and b.system_id = d.system_id(+) and b.prtid = d.prtid(+) order by"
		+ "order by "
		+ HybsSystem.sys( "REPORT_DAEMON_ORDER_BY" );

	private static final String SQL_UPDATE_GE50 =
		"update ge50 set fgkan = ?, dmn_name = ?, dmn_host = ? ,dyupd = ? where system_id = ? and ykno = ?";
	
	private static final String SQL_INSERT_GE56 =
		"insert into ge56 ( fgj, system_id, ykno, errmsg, dyset, dyupd, usrset, usrupd, prgupd ) "
		+ " values ( '1', ?, ? ,? ,? ,? ,? ,? ,? )" ;

	private static final int STATUS_COMPLETE	= 2;
	private static final int STATUS_EXECUTE		= 3;
	private static final int STATUS_ERROR		= 8;

	private static QueueManager manager = new QueueManager_DB();

	/**
	 * インスタンスの生成を禁止します。
	 */
	private QueueManager_DB() {}

	/**
	 * インスタンスを返します。
	 * 
	 * @return 帳票処理キューの管理マネージャ
	 */
	public static QueueManager getInstance() {
		return manager;
	}
	
	/**
	 * 帳票処理キューを作成します。
	 * 
	 * @og.rev 4.3.0.0 (2008/07/15) スレッドIDにシステムIDを付加します。
	 *
	 */
	public synchronized void create() {
		// キューをスタックするまでの例外は、ScheduleTagでcatchされデーモンがスリープする。
		String[][] ge50vals = DBUtil.dbExecute( SQL_SELECT_GE50, new String[0], appInfo );
		
		for( int i=0; i<ge50vals.length; i++ ) {
			ExecQueue queue = new ExecQueue();
			queue.setSystemId( ge50vals[i][0] );
			queue.setYkno( ge50vals[i][1] );
			queue.setListId( ge50vals[i][2] );
			// queue.setOutputName( new File( ge50vals[i][3] ).getAbsolutePath() + File.separator + ge50vals[i][4] );
			queue.setOutputName( new File( ge50vals[i][3] ).getAbsolutePath() , ge50vals[i][4] , ge50vals[i][7] , ge50vals[i][1] ); // 4.3.0.0 (2008/07/18) 要求番号を出力ファイル名に利用
			queue.setPdfPasswd( ge50vals[i][5] );
			queue.setLang( ge50vals[i][6] );
			queue.setOutputType( ge50vals[i][7] );
			// queue.setThreadId( StringUtil.nval( ge50vals[i][8] , "_DEFALUT_" ) );
			queue.setThreadId( ge50vals[i][0] + "_" + StringUtil.nval( ge50vals[i][8] , "_DEFALUT_" ) ); // 4.3.0.0 (2008/07/15)
			queue.setTemplateName( new File( ge50vals[i][9] ).getAbsolutePath() + File.separator + ge50vals[i][10] );
			queue.setPrinterName( ge50vals[i][11] );
			queue.setFglocal( "1".equals( ge50vals[i][12] ) );
			queue.setFgcut( "1".equals( ge50vals[i][13] ) );
			
			queue.setBsql( ge50vals[i][14] );
			queue.setHsql( ge50vals[i][15] );
			queue.setFsql( ge50vals[i][16] );
			
			queue.setManager( this );
			
			ExecThreadManager.insertQueue( queue );
		}
	}
	
	/**
	 * 帳票処理データをキューにセットします。
	 * 
	 * @param queue
	 */
	public void set( final ExecQueue queue ) {

		ResourceManager resource = null;
		if( queue.isFglocal() ) {
			resource = ResourceFactory.newInstance( queue.getSystemId(), queue.getLang(), false );
		}
		else {
			resource = ResourceFactory.newInstance( queue.getLang() );
		}
		String[] where = new String[] { queue.getSystemId() , queue.getYkno() } ;

		// ヘッダー情報の取得
		DBTableModel header = DBTableModelUtil.makeDBTable( queue.getHsql(), where, resource, appInfo );
		if( header != null && header.getRowCount() > 0 ) {
			queue.setHeader( header );
		}

		// フッター情報の取得
		DBTableModel footer = DBTableModelUtil.makeDBTable( queue.getFsql(), where, resource, appInfo );
		if( footer != null && footer.getRowCount() > 0 ) {
			queue.setFooter( footer );
		}

		// ボディー情報の取得
		DBTableModel body = DBTableModelUtil.makeDBTable( queue.getBsql(), where, resource, appInfo );
		// レイアウトテーブルがないと固定長を分割するSQL文が設定されず、DBTableModelがnullになる
		if( body == null ) {
			queue.addMsg( "[ERROR] DBTableModel doesn't exists! maybe Layout-Table(GE52) is not configured..." + HybsSystem.CR );
			queue.setError();
			throw new RuntimeException();
		}
		if( body.getRowCount() <= 0 ) {
			queue.addMsg( "[ERROR] Database Body row count is Zero." + queue.getBsql() + HybsSystem.CR );
			queue.setError();
			throw new RuntimeException();
		}
		if( body.isOverflow() ) {
			queue.addMsg( "[ERROR]Database is Overflow. [" + body.getRowCount() + "]" + HybsSystem.CR );
			queue.addMsg( "[ERROR]Check SystemParameter Data in DB_MAX_ROW_COUNT Overflow" + HybsSystem.CR  );
			queue.setError();
			throw new RuntimeException();
		}
		queue.setBody( body );
	}
	
	/**
	 * キューを実行中の状態に更新します。
	 * 
	 * @param queue
	 */
	public void execute( final ExecQueue queue ) {
		status( queue, STATUS_EXECUTE );
	}
	
	/**
	 * キューを完了済の状態に更新します。
	 * 
	 * @param queue
	 */
	public void complete( final ExecQueue queue ) {
		status( queue, STATUS_COMPLETE );
	}
	
	/**
	 * キューをエラーの状態に更新します。
	 * 
	 * @param queue
	 */
	public void error( final ExecQueue queue ) {
		status( queue, STATUS_ERROR );
		insertErrorMsg( queue );
	}
	
	/**
	 * GE50の状況Cを更新します。
	 * 
	 * @og.rev 4.2.4.1 (2008/07/09) 更新日時をセット
	 * 
	 * @param queue
	 */
	private void status( final ExecQueue queue, final int status ) {

		String dyupd = HybsSystem.getDate( "yyyyMMddHHmmss" ) ;

		String[] args
		= new String[]{ String.valueOf( status ), queue.getThreadId(), HybsSystem.sys( "HOST_NAME" )
				, dyupd , queue.getSystemId(), queue.getYkno() };

		DBUtil.dbExecute( SQL_UPDATE_GE50, args, appInfo );
	}
	
	/**
	 * GE56にエラーメッセージを出力します。
	 * 
	 * @og.rev 4.4.0.1 (2009/08/08) エラーメッセージ機能追加
	 * 
	 * @param queue
	 * @param errMsg
	 */
	private void insertErrorMsg( final ExecQueue queue ) {
		String errmsg = queue.getMsg();
		if( errmsg.length() > 1300 ) {
			errmsg = errmsg.substring( errmsg.length() - 1300, errmsg.length() );
		}

		String dyset = HybsSystem.getDate( "yyyyMMddHHmmss" ) ;

		String[] args
		= new String[]{ queue.getSystemId(), queue.getYkno(), errmsg
				, dyset, dyset, "UNKNOWN", "UNKNOWN", "UNKNOWN" };

		DBUtil.dbExecute( SQL_INSERT_GE56, args, appInfo );

		sendMail( queue, errmsg ); // 4.4.0.1 (2009/08/08)
	}

	/**
	 * エラー情報のメール送信を行います。
	 * エラーメールは、システムパラメータ の COMMON_MAIL_SERVER(メールサーバー)と
	 * ERROR_MAIL_FROM_USER(エラーメール発信元)と、ERROR_MAIL_TO_USERS(エラーメール受信者)
	 * がすべて設定されている場合に、送信されます。
	 *
	 * @og.rev 4.4.0.1 (2009/08/08) 追加
	 *
	 * @param inErrMsg String エラーメッセージ
	 */
	private void sendMail( final ExecQueue queue, final String inErrMsg ) {

		String   host = HybsSystem.sys( "COMMON_MAIL_SERVER" );
		String   from = HybsSystem.sys( "ERROR_MAIL_FROM_USER" );
		String[] to = StringUtil.csv2Array( HybsSystem.sys( "ERROR_MAIL_TO_USERS" ) );
		if( host != null && from != null && to.length > 0 ) {
			String subject = "SYSTEM_ID=[" + queue.getSystemId() + "] , YKNO=[" + queue.getYkno() + "] , "
						   + "THREAD_ID=[" + queue.getThreadId() + "] , DMN_HOST=[" + HybsSystem.HOST_NAME + "]" ;
			try {
				MailTX tx = new MailTX( host );
				tx.setFrom( from );
				tx.setTo( to );
				tx.setSubject( "帳票エラー：" + subject );
				tx.setMessage( inErrMsg );
				tx.sendmail();
			}
			catch( Throwable ex ) {
				String errMsg = "エラー時メール送信に失敗しました。" + HybsSystem.CR
							+ subject + HybsSystem.CR
							+ ex.getMessage();
				LogWriter.log( errMsg );
				LogWriter.log( ex );
			}
		}
	}
}
