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

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

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

/**
 * ファイルリスト管理.
 * 
 * @version 2008/07/29
 * @author masahito suzuki
 * @since MaachangComet 1.20
 */
public class FileListManager {
    
    /**
     * ファイルリスト破棄時間.
     * 3分.
     */
    private static final long DESTROY_LIST_TIME = 180000L ;
    
    /**
     * リスト管理ファイル名.
     */
    private static final String LIST_MAN = ".mlst" ;
    
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( FileListManager.class ) ;
    
    /**
     * シングルトン.
     */
    private static final FileListManager SNGL = new FileListManager() ;
    
    /**
     * データ管理.
     */
    private Map<String,FileListChild> manager = null ;
    
    /**
     * リストデータ破棄監視.
     */
    private DestroyFileListThread mon = null ;
    
    /**
     * 同期オブジェクト.
     */
    private final DirLock sync = new DirLock() ;
    
    /**
     * オブジェクトを取得.
     * @return FileListManager オブジェクトが返されます.
     */
    public static final FileListManager getInstance() {
        return SNGL ;
    }
    
    /**
     * コンストラクタ.
     */
    private FileListManager() {
        try {
            manager = Collections.synchronizedMap( new HashMap<String,FileListChild>() ) ;
            mon = new DestroyFileListThread( DESTROY_LIST_TIME,manager,sync ) ;
        } catch( Exception e ) {
            manager = null ;
            mon = null ;
            LOG.error( "error",e ) ;
        }
    }
    
    /**
     * データ一覧に情報を追加.
     * @param dir 対象のディレクトリ名を設定します.
     * @param name 追加ファイル名を設定します.
     * @exception Exception 例外.
     */
    public void addList( String dir,String name )
        throws Exception {
        if( dir == null || ( dir = dir.trim() ).length() <= 0 ||
            name == null || ( name = name.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        dir = FileUtil.getFullPath( dir ) ;
        if( dir.endsWith( "/" ) == false && dir.endsWith( "\\" ) == false ) {
            dir += FileUtil.FILE_SPACE ;
        }
        String file = dir + LIST_MAN ;
        sync.lock( dir ) ;
        try {
            if( manager.containsKey( dir ) ) {
                manager.remove( dir ) ;
            }
            if( FileUtil.isDirExists( dir ) == false ) {
                FileUtil.mkdirs( dir ) ;
            }
            if( FileUtil.isFileExists( file ) && FileUtil.getLength( file ) > 0 ) {
                RandomAccessFile fp = null ;
                try {
                    fp = new RandomAccessFile( file,"rwd" ) ;
                    long size = fp.length() ;
                    fp.seek( size ) ;
                    fp.write( new StringBuilder().append( "\n" ).append( name ).toString().getBytes( "UTF8" ) ) ;
                    fp.close() ;
                    fp = null ;
                } finally {
                    if( fp != null ) {
                        try {
                            fp.close() ;
                        } catch( Exception e ) {
                        }
                        fp = null ;
                    }
                }
            }
            else {
                RandomAccessFile fp = null ;
                try {
                    fp = new RandomAccessFile( file,"rwd" ) ;
                    fp.write( name.getBytes( "UTF8" ) ) ;
                    fp.close() ;
                    fp = null ;
                } finally {
                    if( fp != null ) {
                        try {
                            fp.close() ;
                        } catch( Exception e ) {
                        }
                        fp = null ;
                    }
                }
            }
        } finally {
            sync.unlock( dir ) ;
        }
    }
    
    /**
     * データ一覧から指定名の内容を削除.
     * @param dir 対象のディレクトリ名を設定します.
     * @param name 削除対象の名前を設定します.
     * @exception Exception 例外.
     */
    public void removeList( String dir,String name )
        throws Exception {
        if( dir == null || ( dir = dir.trim() ).length() <= 0 ||
            name == null || ( name = name.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        dir = FileUtil.getFullPath( dir ) ;
        if( dir.endsWith( "/" ) == false && dir.endsWith( "\\" ) == false ) {
            dir += FileUtil.FILE_SPACE ;
        }
        sync.lock( dir ) ;
        try {
            if( manager.containsKey( dir ) ) {
                manager.remove( dir ) ;
            }
            String file = dir + LIST_MAN ;
            String[] list = getListFile( file ) ;
            if( list != null ) {
                int len = list.length ;
                boolean removeFlag = false ;
                for( int i = 0 ; i < len ; i ++ ) {
                    if( name.equals( list[ i ] ) ) {
                        removeFlag = true ;
                        list[ i ] = null ;
                        break ;
                    }
                }
                if( removeFlag ) {
                    BufferedWriter bw = null ;
                    try {
                        bw = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( file ),"UTF8" ) ) ;
                        int cnt = 0 ;
                        for( int i = len-1 ; i >= 0 ; i -- ) {
                            if( list[ i ] == null ) {
                                continue ;
                            }
                            if( cnt != 0 ) {
                                bw.write( "\n" ) ;
                            }
                            bw.write( list[ i ] ) ;
                            cnt ++ ;
                        }
                        bw.flush() ;
                        bw.close() ;
                        bw = null ;
                    } finally {
                        if( bw != null ) {
                            try {
                                bw.close() ;
                            } catch( Exception e ) {
                            }
                        }
                    }
                }
            }
        } finally {
            sync.unlock( dir ) ;
        }
    }
    
    /**
     * データ一覧を取得.
     * @param dir 対象のディレクトリ名を設定します.
     * @return String[] データ一覧が返されます.
     * @exception Exception 例外.
     */
    public String[] getList( String dir )
        throws Exception {
        if( dir == null || ( dir = dir.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        dir = FileUtil.getFullPath( dir ) ;
        if( dir.endsWith( "/" ) == false && dir.endsWith( "\\" ) == false ) {
            dir += FileUtil.FILE_SPACE ;
        }
        String[] ret = null ;
        sync.lock( dir ) ;
        try {
            if( manager.containsKey( dir ) ) {
                FileListChild ch = manager.get( dir ) ;
                ch.access() ;
                ret = ch.getList() ;
            }
            else {
                String file = dir + LIST_MAN ;
                ret = getListFile( file ) ;
                if( ret != null ) {
                    FileListChild ch = new FileListChild() ;
                    ch.setList( ret ) ;
                    ch.access() ;
                    manager.put( dir,ch ) ;
                }
            }
        } finally {
            sync.unlock( dir ) ;
        }
        return ret ;
    }
    
    /**
     * リストファイルを読み込む.
     */
    private String[] getListFile( String file ) throws Exception {
        String[] ret = null ;
        if( FileUtil.isFileExists( file ) ) {
            BufferedReader br = null ;
            ArrayList<String> lst = null ;
            try {
                br = new BufferedReader( new InputStreamReader( new FileInputStream( file ),"UTF8" ) ) ;
                lst = new ArrayList<String>() ;
                for( ;; ) {
                    String s = br.readLine() ;
                    if( s == null ) {
                        break ;
                    }
                    lst.add( s ) ;
                }
                br.close() ;
                br = null ;
                int len = lst.size() ;
                if( len > 0 ) {
                    ret = new String[ len ] ;
                    int cnt = 0 ;
                    for( int i = len-1 ; i >= 0 ; i -- ) {
                        ret[ cnt ] = lst.get( i ) ;
                        cnt ++ ;
                    }
                }
                lst = null ;
            } finally {
                if( br != null ) {
                    try {
                        br.close() ;
                    } catch( Exception e ) {
                    }
                    br = null ;
                }
            }
        }
        return ret ;
    }
    
    /**
     * オブジェクトが有効かチェック.
     * @return boolean [true]の場合有効です.
     */
    public synchronized boolean isUse() {
        return ( manager != null && mon != null && mon.isStop() == false ) ;
    }
}

/**
 * １つのリスト管理.
 */
class FileListChild {
    private String[] list = null ;
    private long lastAccess = -1 ;
    
    public synchronized void setList( String[] list ) {
        this.list = list ;
    }
    public synchronized String[] getList() {
        return this.list ;
    }
    public synchronized void setLastAccess( long lastAccess ) {
        this.lastAccess = lastAccess ;
    }
    public synchronized long getLastAccess() {
        return this.lastAccess ;
    }
    public synchronized void access() {
        this.lastAccess = System.currentTimeMillis() ;
    }
}

/**
 * ディレクトリ同期管理.
 */
class DirLock {
    private Set<String> sync = null ;
    public DirLock() {
        sync = new HashSet<String>() ;
    }
    public void lock( String id ) throws Exception {
        if( id == null || ( id = id.trim() ).length() <= 0 ) {
            return ;
        }
        for( ;; ) {
            synchronized( this ) {
                if( sync.contains( id ) == false ) {
                    sync.add( id ) ;
                    break ;
                }
            }
            Thread.sleep( 15L ) ;
        }
    }
    public void unlock( String id ) {
        if( id == null || ( id = id.trim() ).length() <= 0 ) {
            return ;
        }
        synchronized( this ) {
            sync.remove( id ) ;
        }
    }
}

/**
 * データ破棄監視.
 */
class DestroyFileListThread extends LoopThread {
    private long destroyTime = -1 ;
    private Map<String,FileListChild> manager = null ;
    private DirLock sync = null ;
    
    private DestroyFileListThread() {}
    
    public DestroyFileListThread( long destroyTime,Map<String,FileListChild> manager,DirLock sync )
        throws Exception {
        this.destroyTime = destroyTime ;
        this.manager = manager ;
        this.sync = sync ;
        this.startThread() ;
    }
    
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    public void destroy() {
        super.stopThread() ;
    }
    
    protected void clear() {
        
    }
    
    protected boolean execution() throws Exception {
        Iterator<String> it = manager.keySet().iterator() ;
        long now = System.currentTimeMillis() ;
        while( it.hasNext() ) {
            if( super.isStop() == true ) {
                break ;
            }
            String key = it.next() ;
            if( key != null ) {
                sync.lock( key ) ;
                try {
                    FileListChild ch = manager.get( key ) ;
                    if( ch == null ) {
                        it.remove() ;
                    }
                    else if( ch.getLastAccess() + destroyTime <= now ) {
                        it.remove() ;
                    }
                } catch( Exception e ) {
                } finally {
                    sync.unlock( key ) ;
                }
            }
            Thread.sleep( 100L ) ;
            now += 100L ;
        }
        return false ;
    }
}

