package org.maachang.comet.httpd.engine;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.comet.httpd.HttpdErrorDef;
import org.maachang.comet.httpd.HttpdStateException;
import org.maachang.util.FileUtil;
import org.maachang.util.thread.LoopThread;


/**
 * Httpdパブリックキャッシュ.
 * 
 * @version 2008/08/09
 * @author masahito suzuki
 * @since MaachangComet 1.22
 */
class PublicCache {
    
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( PublicCache.class ) ;
    
    /**
     * シングルトン.
     */
    private static final PublicCache SNGL = new PublicCache() ;
    
    /**
     * キャッシュ情報格納オブジェクト.
     */
    private Map<String,PublicCacheChild> manager = null ;
    
    /**
     * スレッド監視用.
     */
    private PublicCacheChildThread thread = null ;
    
    /**
     * 同期オブジェクト.
     */
    private final Object sync = new Object() ;
    
    /**
     * コンストラクタ.
     */
    private PublicCache() {
        try {
            manager = Collections.synchronizedMap( new HashMap<String,PublicCacheChild>() ) ;
            thread = new PublicCacheChildThread( manager,sync ) ;
        } catch( Exception e ) {
            LOG.error( "error",e ) ;
        }
    }
    
    /**
     * オブジェクトを取得.
     * @return PublicCache オブジェクトが返されます.
     */
    public static final PublicCache getInstance() {
        return SNGL ;
    }
    
    /**
     * 指定パスのデータを取得.
     * @param path 対象のパスを設定します.
     * @return byte[] データが返されます.
     * @exception Exception 例外.
     */
    public byte[] getData( String path ) throws Exception {
        PublicCacheChild ch = get( path ) ;
        if( ch == null ) {
            return null ;
        }
        return ch.getData() ;
    }
    
    /**
     * 指定パスのファイル時間を取得.
     * @param path 対象のパスを設定します.
     * @return long 時間が返されます.
     * @exception Exception 例外.
     */
    public long getLastTime( String path ) throws Exception {
        PublicCacheChild ch = get( path ) ;
        if( ch == null ) {
            return -1L ;
        }
        return ch.getLastUpdateTime() ;
    }
    
    private PublicCacheChild get( String path ) throws Exception {
        if( path == null || ( path = path.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        PublicCacheChild ret = null ;
        if( manager.containsKey( path ) == false ) {
            synchronized( sync ) {
                if( FileUtil.isFileExists( path ) == true ) {
                    if( FileUtil.isRead( path ) == false ) {
                        throw new HttpdStateException( HttpdErrorDef.HTTP11_403,
                            "指定パス[" + path + "]は読み込み権限がありません" ) ;
                    }
                    ret = new PublicCacheChild(
                        path,
                        FileUtil.getFile( path ),
                        FileUtil.getLastTime( path ) ) ;
                    if( LOG.isDebugEnabled() ) {
                        LOG.debug( "## 指定ファイル[" + path + "]をキャッシュしました" ) ;
                    }
                    manager.put( path,ret ) ;
                }
            }
        }
        else {
            ret = manager.get( path ) ;
            if( ret != null ) {
                ret.update() ;
            }
        }
        return ret ;
    }
    
    /**
     * オブジェクトが有効かチェック.
     * @return boolean [true]の場合は有効です.
     */
    public boolean isUse() {
        boolean ret = true ;
        synchronized( sync ) {
            ret = ( thread.isStop() == false ) ? true : false ;
        }
        return ret ;
    }
}

/**
 * キャッシュ子要素.
 */
class PublicCacheChild {
    private static final long DELETE_TIME = 900000L ;
    private byte[] data = null ;
    private long lastUpdateTime = -1L ;
    private String path = null ;
    private long lastAccessTime = -1L ;
    
    private PublicCacheChild() {
        
    }
    public PublicCacheChild( String path,byte[] data,long lastUpdateTime ) {
        this.path = path ;
        this.data = data ;
        this.lastUpdateTime = lastUpdateTime ;
        this.lastAccessTime = System.currentTimeMillis() + DELETE_TIME ;
    }
    public void set( byte[] data,long lastUpdateTime ) {
        this.data = data ;
        this.lastUpdateTime = lastUpdateTime ;
    }
    public String getPath() {
        return path ;
    }
    public byte[] getData() {
        return data ;
    }
    public long getLastUpdateTime() {
        return lastUpdateTime ;
    }
    public synchronized void update() {
        this.lastAccessTime = System.currentTimeMillis() + DELETE_TIME ;
    }
    public synchronized long getUpdateTime() {
        return lastAccessTime ;
    }
}

/**
 * データ確認用.
 */
class PublicCacheChildThread extends LoopThread {
    private Map<String,PublicCacheChild> manager = null ;
    private Object sync = null ;
    public PublicCacheChildThread( Map<String,PublicCacheChild> manager,Object sync )
        throws Exception {
        this.manager = manager ;
        this.sync = sync ;
        super.startThread() ;
    }
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    public void destroy() {
        super.stopThread() ;
    }
    protected void clear() {
        manager = null ;
        sync = null ;
    }
    protected boolean execution() throws Exception {
        Iterator itr = manager.keySet().iterator() ;
        while(itr.hasNext()) {
            if( super.isStop() ) {
                break ;
            }
            Thread.sleep( 100L ) ;
            String key = ( String )itr.next() ;
            PublicCacheChild ch = manager.get( key ) ;
            if( ch == null ) {
                continue ;
            }
            long time = -1L ;
            if( FileUtil.isFileExists( ch.getPath() ) == false ) {
                synchronized( sync ) {
                    itr.remove() ;
                }
                continue ;
            }
            else if( ch.getUpdateTime() <= System.currentTimeMillis() ) {
                synchronized( sync ) {
                    itr.remove() ;
                }
                continue ;
            }
            else if( ( time = FileUtil.getLastTime( ch.getPath() ) ) != ch.getLastUpdateTime() ) {
                byte[] bin = FileUtil.getFile( ch.getPath() ) ;
                synchronized( sync ) {
                    ch.set( bin,time ) ;
                }
            }
        }
        return false ;
    }
}
