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

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.script.Bindings;
import javax.script.SimpleBindings;

/**
 * キャッシュテーブル.
 * 
 * @version 2007/09/23
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class CacheTable implements Map<String,Object> {
    
    /**
     * マージ用テーブル.
     */
    private HashMap<String,Bindings> marge = null ;
    
    /**
     * データ追加用.
     */
    private Bindings bindings = null ;
    
    /**
     * アクセス用同期.
     */
    private final Object sync = new Object() ;
    
    /**
     * 更新中フラグ.
     */
    private volatile boolean updateTo = false ;
    
    /**
     * 更新スレッド名.
     */
    private String updateThreadName = null ;
    
    /**
     * 更新中同期.
     */
    private final Object updateSync = new Object() ;
    
    
    /**
     * コンストラクタ.
     */
    public CacheTable() {
    }
    
    /**
     * 更新中に設定できるまで待機.
     */
    public void waitByStartUpdate() {
        try {
            for( ;; ) {
                if( startUpdate() == false ) {
                    Thread.sleep( 30L ) ;
                    waitUpdate() ;
                    continue ;
                }
                break ;
            }
        } catch( Exception e ) {
        }
    } 
    
    /**
     * 更新中に設定.
     */
    public boolean startUpdate() {
        synchronized( updateSync ) {
            if( updateThreadName != null ) {
                return false ;
            }
            updateTo = true ;
            updateThreadName = Thread.currentThread().getName() ;
            return true ;
        }
    }
    
    /**
     * 更新終了に設定.
     */
    public void exitUpdate() {
        synchronized( updateSync ) {
            if( updateThreadName != null &&
                updateThreadName.equals( Thread.currentThread().getName() ) ) {
                updateTo = false ;
                updateThreadName = null ;
                synchronized( sync ) {
                    bindings = null ;
                }
            }
        }
    }
    
    /**
     * 更新フラグを取得.
     * <BR><BR>
     * 更新フラグを取得します.
     * <BR>
     * @return boolean [true]の場合は更新中です.
     */
    public boolean isUpdate() {
        boolean ret = false ;
        synchronized( updateSync ) {
            if( updateThreadName != null &&
                updateThreadName.equals( Thread.currentThread().getName() ) ) {
                return false ;
            }
            ret = updateTo ;
        }
        return ret ;
    }
    
    /**
     * 更新待ち処理.
     * <BR><BR>
     * 更新中の場合は、待ち処理が実施されます.
     */
    public void waitUpdate() {
        try {
            for( ;; ) {
                if( isUpdate() == true ) {
                    Thread.sleep( 30L ) ;
                    continue ;
                }
                break ;
            }
        } catch( Exception e ) {
        }
    }
    
    /**
     * 全データクリア.
     * <BR><BR>
     * 全データをクリアします.
     */
    public void clearAll() {
        waitUpdate() ;
        synchronized( sync ) {
            marge = null ;
            bindings = null ;
        }
    }
    
    /**
     * 追加用データをクリア.
     * <BR><BR>
     * 追加用データをクリアします.
     */
    public void clearBindings() {
        waitUpdate() ;
        synchronized( sync ) {
            bindings = null ;
        }
    }
    
    /**
     * 追加用データを取得.
     * <BR><BR>
     * 追加用データを取得します.<BR>
     * また、取得と同時にクリアされます.
     * <BR>
     * @return Bindings 追加用データが返されます.
     */
    public Bindings getBindings() {
        waitUpdate() ;
        Bindings ret = null ;
        synchronized( sync ) {
            ret = bindings ;
            bindings = null ;
        }
        return ret ;
    }
    
    /**
     * 新しい親キャッシュ情報をマージ.
     * <BR><BR>
     * 新しい親キャッシュ情報をマージします.
     * <BR>
     * @param name 対象の名前を設定します.
     * @param value マージ対象の親情報を設定します.
     */
    public void putParent( String name,Bindings value ) {
        waitUpdate() ;
        if( name == null || value == null || ( name = name.trim() ).length() <= 0 ) {
            return ;
        }
        synchronized( sync ) {
            if( marge == null ) {
                marge = new HashMap<String,Bindings>() ;
            }
            name = name.toLowerCase() ;
            marge.put( name,value ) ;
        }
    }
    
    /**
     * 指定親キャッシュが存在するかチェック.
     * <BR><BR>
     * 指定された親キャッシュが存在するかチェックします.
     * <BR>
     * @param name 対象の名前を設定します.
     * @return boolean [true]の場合存在します.
     */
    public boolean isParent( String name ) {
        waitUpdate() ;
        if( name == null || ( name = name.trim() ).length() <= 0 ) {
            return true ;
        }
        boolean ret = false ;
        synchronized( sync ) {
            if( marge == null ) {
                marge = new HashMap<String,Bindings>() ;
            }
            name = name.toLowerCase() ;
            if( marge.get( name ) == null ) {
                ret = false ;
            }
            else {
                ret = true ;
            }
        }
        return ret ;
    }
    
    /**
     * 全キー名を取得.
     * <BR><BR>
     * 全てのキー名群を取得します.
     * <BR>
     * @return HashSet<String> 全キー名が返されます.
     */
    protected HashSet<String> getAllKey() {
        HashSet<String> ret = new HashSet<String>() ;
        synchronized( sync ) {
            Object[] names = getNames() ;
            if( names != null ) {
                int len = names.length ;
                for( int i = 0 ; i < len ; i ++ ) {
                    Bindings m = marge.get( ( String )names[ i ] ) ;
                    if( m != null && m.size() > 0 ) {
                        int lenJ = m.size() ;
                        Object[] nms = m.keySet().toArray() ;
                        for( int j = 0 ; j < lenJ ; j ++ ) {
                            ret.add( ( String )nms[ j ] ) ;
                        }
                    }
                }
            }
        }
        return ret ;
    }
    
    public void clear() {
        clearBindings() ;
    }
    
    public int size() {
        waitUpdate() ;
        int ret = 0 ;
        synchronized( sync ) {
            Object[] names = getNames() ;
            if( names != null ) {
                int len = names.length ;
                for( int i = 0 ; i < len ; i ++ ) {
                    Bindings b = marge.get( ( String )names[ i ] ) ;
                    if( b != null ) {
                        ret += b.size() ;
                    }
                }
            }
            if( bindings != null ) {
                ret += bindings.size() ;
            }
        }
        return ret ;
    }
    
    public boolean isEmpty() {
        waitUpdate() ;
        boolean ret = false ;
        synchronized( sync ) {
            ret = ( marge == null && bindings == null ) ? true : false ;
        }
        return ret ;
    }
    
    public boolean containsKey(Object key) {
        waitUpdate() ;
        synchronized( sync ) {
            Object[] names = getNames() ;
            if( names != null ) {
                int len = names.length ;
                for( int i = 0 ; i < len ; i ++ ) {
                    Bindings b = marge.get( ( String )names[ i ] ) ;
                    if( b != null && b.containsKey( key ) == true ) {
                        return true ;
                    }
                }
            }
            if( bindings != null && bindings.containsKey( key ) == true ) {
                return true ;
            }
        }
        return false ;
    }
    
    public boolean containsValue(Object value) {
        waitUpdate() ;
        synchronized( sync ) {
            Object[] names = getNames() ;
            if( names != null ) {
                int len = names.length ;
                for( int i = 0 ; i < len ; i ++ ) {
                    Bindings b = marge.get( ( String )names[ i ] ) ;
                    if( b != null && b.containsValue( value ) == true ) {
                        return true ;
                    }
                }
            }
            if( bindings != null && bindings.containsValue( value ) == true ) {
                return true ;
            }
        }
        return false ;
    }
    
    public Object get(Object key) {
        waitUpdate() ;
        synchronized( sync ) {
            Object[] names = getNames() ;
            if( names != null ) {
                int len = names.length ;
                for( int i = 0 ; i < len ; i ++ ) {
                    Bindings b = marge.get( ( String )names[ i ] ) ;
                    if( b != null ) {
                        Object o = b.get( key ) ;
                        if( o != null ) {
                            return o ;
                        }
                    }
                }
            }
            if( bindings != null ) {
                return bindings.get( key ) ;
            }
        }
        return null ;
    }
    
    public Object put(String key, Object value) {
        waitUpdate() ;
        synchronized( sync ) {
            Object[] names = getNames() ;
            if( names != null ) {
                int len = names.length ;
                for( int i = 0 ; i < len ; i ++ ) {
                    Bindings b = marge.get( ( String )names[ i ] ) ;
                    if( b != null && b.containsKey( key ) == true ) {
                        return b.put( key,value ) ;
                    }
                }
            }
            if( bindings != null ) {
                return bindings.put( key,value ) ;
            }
            else {
                bindings = new SimpleBindings() ;
                return bindings.put( key,value ) ;
            }
        }
    }
    
    public Object remove(Object key) {
        waitUpdate() ;
        synchronized( sync ) {
            Object[] names = getNames() ;
            if( names != null ) {
                int len = names.length ;
                for( int i = 0 ; i < len ; i ++ ) {
                    Bindings b = marge.get( ( String )names[ i ] ) ;
                    if( b != null && b.containsKey( key ) == true ) {
                        return b.remove( key ) ;
                    }
                }
            }
            if( bindings != null && bindings.containsKey( key ) == true ) {
                return bindings.remove( key ) ;
            }
        }
        return null ;
    }
    
    public void putAll(Map<? extends String, ? extends Object> m) {
    }
    
    public Set<String> keySet() {
        return null ;
    }
    
    public Collection<Object> values() {
        return null ;
    }
    
    public Set<Map.Entry<String, Object>> entrySet() {
        return null ;
    }
    
    public boolean equals(Map<String,Object> o) {
        return false ;
    }
    
    public int hashCode() {
        return 1 ;
    }
    
    private Object[] getNames() {
        if( marge != null && marge.size() > 0 ) {
            Object[] names = marge.keySet().toArray() ;
            Arrays.sort( names ) ;
            return names ;
        }
        return null ;
    }
}
