package org.maachang.comet.httpd.engine.script.cron;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.util.thread.LoopThread;

/**
 * クーロン(スケジューリング実行)タイマー.
 * 
 * @version 2008/07/01
 * @author masahito suzuki
 * @since MaachangComet 1.1F
 */
public class CronTiemrThread extends LoopThread {
    
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( CronTiemrThread.class ) ;
    
    /**
     * 次回実行可能時間.
     */
    private static final long NEXT_RES_TIME = 60L * 1000L ;
    
    /**
     * 次回実行可能時間.
     */
    //private static final long RES_TIME = 90L * 1000L ;
    private static final long RES_TIME = 0L ;
    
    /**
     * スケジュール予約用.
     */
    private List<CronBean> res = null ;
    
    /**
     * スケジュール実行用.
     */
    private List<CronBean> executions = null ;
    
    /**
     * 実行中Thread.
     */
    private List<CronOneThread> threads = null ;
    
    /**
     * スケジュールコンフィグ.
     */
    private CronConfig config = null ;
    
    private final boolean[] out = new boolean[ 1 ] ;
    
    /**
     * コンストラクタ.
     */
    public CronTiemrThread() throws Exception {
        this.res = Collections.synchronizedList( new ArrayList<CronBean>() ) ;
        this.executions = Collections.synchronizedList( new ArrayList<CronBean>() ) ;
        this.threads = Collections.synchronizedList( new ArrayList<CronOneThread>() ) ;
        this.config = new CronConfig() ;
        this.config.open() ;
        this.startThread() ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public synchronized void destroy() {
        super.stopThread() ;
        // 実行中スレッドが存在する場合は、待機.
        if( this.threads != null ) {
            LOG.info( "Cron実行スレッド待機:開始" ) ;
            long time = System.currentTimeMillis() ;
            int cnt = 0 ;
            for( ;; ) {
                int len = threads.size() ;
                for( int i = len - 1 ; i >= 0 ; i -- ) {
                    CronOneThread th = threads.get( i ) ;
                    if( th == null || th.isStop() == true ) {
                        threads.remove( i ) ;
                    }
                }
                if( threads.size() <= 0 ) {
                    break ;
                }
                if( ( cnt % 100 ) == 99 ) {
                    LOG.info( "Cron実行スレッド待機中" ) ;
                }
                try {
                    Thread.sleep( 50L ) ;
                } catch( Exception e ) {
                }
                cnt ++ ;
            }
            time = System.currentTimeMillis() - time ;
            LOG.info( "Cron実行スレッド待機:終了 - " + time + "ms" ) ;
        }
        this.threads = null ;
        this.res = null ;
        this.executions = null ;
    }
    
    /**
     * オブジェクトクリア処理.
     * <p>スレッドが停止したときに呼び出されます.</p>
     */
    protected void clear() {
        if( this.config != null ) {
            config.close() ;
        }
        config = null ;
        executions = null ;
    }
    
    /**
     * 実行処理.
     * @exception Exception 例外.
     */
    protected boolean execution() throws Exception {
        Thread.sleep( 50L ) ;
        int len = config.size( out ) ;
        updateConfig() ;
        if( len > 0 ) {
            Calendar cal = Calendar.getInstance() ;
            cal.setTime( new Date( System.currentTimeMillis() + RES_TIME ) ) ;
            // 実行可能な予約処理.
            for( int i = 0 ; i < len ; i ++ ) {
                CronBean bean = config.get( out,i ) ;
                updateConfig() ;
                if( bean == null ) {
                    continue ;
                }
                // 予約可能な時間であるかチェック.
                if( bean.getLastExecutionTime() + NEXT_RES_TIME <= System.currentTimeMillis() ) {
                    // 指定時間範囲前に実行可能な場合.
                    bean = useExecution( false,cal,bean ) ;
                    // 実行予約する.
                    if( bean != null ) {
                        bean.setLastExecutionTime( System.currentTimeMillis() ) ;
                        res.add( bean ) ;
                    }
                }
            }
        }
        // スレッド実行が終了しているものは、破棄.
        len = threads.size() ;
        for( int i = len - 1 ; i >= 0 ; i -- ) {
            try {
                CronOneThread th = threads.get( i ) ;
                if( th == null || th.isStop() == true ) {
                    threads.remove( i ) ;
                }
            } catch( Exception e ) {
            }
        }
        // 実行予約の実行処理.
        len = res.size() ;
        if( len > 0 ) {
            Calendar cal = Calendar.getInstance() ;
            cal.setTime( new Date( System.currentTimeMillis() ) ) ;
            for( int i = len-1 ; i >= 0 ; i -- ) {
                CronBean bean = res.get( i ) ;
                // 実行可能時間になった場合.
                bean = useExecution( true,cal,bean ) ;
                // Thread実行する.
                if( bean != null ) {
                    res.remove( i ) ;
                    CronOneThread th = new CronOneThread( executions,bean ) ;
                    threads.add( th ) ;
                    th.start() ;
                }
            }
        }
        return false ;
    }
    
    /**
     * 指定内容が実行可能かチェック.
     */
    private CronBean useExecution( boolean mode,Calendar cal,CronBean bean ) throws Exception {
        // 予約内に、同一Beanが存在するかチェックする場合.
        if( mode == false ) {
            // 既に予約中の場合は処理しない.
            int len = res.size() ;
            for( int i = 0 ; i < len ; i ++ ) {
                CronBean x = res.get( i ) ;
                if( bean.equals( x ) ) {
                    return null ;
                }
            }
        }
        // 既に実行中の場合は処理しない.
        int len = executions.size() ;
        for( int i = 0 ; i < len ; i ++ ) {
            CronBean x = executions.get( i ) ;
            if( bean.equals( x ) ) {
                return null ;
            }
        }
        
        boolean check = false ;
        
        // 月チェック.
        if( bean.getMonth() != -1 ) {
            check = true ;
            if( cal.get(Calendar.MONTH)+1 != bean.getMonth() ) {
                return null ;
            }
        }
        
        // 曜日チェック.
        if( bean.getWeek() != -1 ) {
            check = true ;
            switch( cal.get(Calendar.DAY_OF_WEEK) ) {
                case Calendar.SUNDAY : if( bean.getWeek() != 0 ) { return null ; } break ;
                case Calendar.MONDAY : if( bean.getWeek() != 1 ) { return null ; } break ;
                case Calendar.TUESDAY : if( bean.getWeek() != 2 ) { return null ; } break ;
                case Calendar.WEDNESDAY : if( bean.getWeek() != 3 ) { return null ; } break ;
                case Calendar.THURSDAY : if( bean.getWeek() != 4 ) { return null ; } break ;
                case Calendar.FRIDAY : if( bean.getWeek() != 5 ) { return null ; } break ;
                case Calendar.SATURDAY : if( bean.getWeek() != 6 ) { return null ; } break ;
            }
        }
        
        // 日付チェック.
        if( bean.getDate() != -1 || bean.getDateTm() != -1 ) {
            check = true ;
            if( bean.getDate() != -1 ) {
                if( cal.get(Calendar.DAY_OF_MONTH) != bean.getDate() ) {
                    return null ;
                }
            }
            else if( bean.getDateTm() != -1 ) {
                if( ( cal.get(Calendar.DAY_OF_MONTH) % bean.getDateTm() ) != 0 ) {
                    return null ;
                }
            }
        }
        
        // 時間チェック.
        if( bean.getHour() == -1 && bean.getHourTm() == -1 ) {
            if( check == true && cal.get(Calendar.HOUR_OF_DAY) != 0 ) {
                return null ;
            }
        }
        else {
            check = true ;
            if( bean.getHour() != -1 ) {
                if( cal.get(Calendar.HOUR_OF_DAY) != bean.getHour() ) {
                    return null ;
                }
            }
            else if( bean.getHourTm() != -1 ) {
                if( ( cal.get(Calendar.HOUR_OF_DAY) % bean.getHourTm() ) != 0 ) {
                    return null ;
                }
            }
        }
        
        // 分チェック.
        if( bean.getMinute() == -1 && bean.getMinuteTm() == -1 ) {
            if( check == true && cal.get(Calendar.MINUTE) != 0 ) {
                return null ;
            }
        }
        else {
            check = true ;
            if( bean.getMinute() != -1 ) {
                if( cal.get(Calendar.MINUTE) != bean.getMinute() ) {
                    return null ;
                }
            }
            else if( bean.getMinuteTm() != -1 ) {
                if( ( cal.get(Calendar.MINUTE) % bean.getMinuteTm() ) != 0 ) {
                    return null ;
                }
            }
        }
        
        if( check == false ) {
            return null ;
        }
        return bean ;
        
    }
    
    /**
     * 更新条件が存在する場合の処理.
     */
    private void updateConfig() {
        if( out[ 0 ] == true ) {
            res.clear() ;
            executions.clear() ;
        }
    }
}
