package org.maachang.mimdb ;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.maachang.mimdb.core.MimdbTableInstance;
import org.maachang.mimdb.core.MimdbTableManager;
import org.maachang.mimdb.core.impl.MimdbUtils;
import org.maachang.mimdb.core.util.Config;

/**
 * Mimdb組み込みデータベース.
 * 
 * @version 2013/11/07
 * @author masahito suzuki
 * @since MasterInMemDB 1.00
 */
public final class MimdbDatabase {
    private Config conf = null ;
    
    private String baseFolder = null ;
    private Map<String,String> tableConfigList = null ;
    private String[] tableNames = null ;
    private String name = null ;
    
    private boolean useFlag = false ;
    
    /**
     * ReadWriteロック用オブジェクト.
     */
    private ReentrantReadWriteLock sync = new ReentrantReadWriteLock() ;
    
    /**
     * コンストラクタ.
     */
    private MimdbDatabase() {}
    
    /**
     * コンストラクタ.
     * @param name 対象のembedded.confファイル名を設定します.
     * @exception Exception 例外.
     */
    public MimdbDatabase( String name ) throws Exception {
        // 別名定義されている場合は、別名で読み込む.
        this( Config.read( ( Mimdb.getInstance().aliasNames.containsKey( name.toLowerCase() ) ) ?
            Mimdb.getInstance().aliasNames.get( name.toLowerCase() ) : name ) ) ;
    }
    
    /**
     * コンストラクタ.
     * @param conf 対象のembedded.conf情報を設定します.
     * @exception Exception 例外.
     */
    public MimdbDatabase( Config conf ) throws Exception {
        // mimdb利用可能かチェック.
        if( isUse( conf ) ) {
            sync.writeLock().lock() ;
            try {
                // 初期処理.
                init( conf ) ;
                
                // 読み込み先のコンフィグ条件を保持.
                this.conf = conf ;
                useFlag = true ;
            } finally {
                sync.writeLock().unlock() ;
            }
            // テーブル登録.
            Mimdb.getInstance().manager.put( name,this ) ;
        }
    }
    
    /** 利用可能かチェック. **/
    protected static final boolean isUse( String name ) throws Exception {
        // 別名定義されている場合は、別名で読み込む.
        return isUse( Config.read( ( Mimdb.getInstance().aliasNames.containsKey( name.toLowerCase() ) ) ?
            Mimdb.getInstance().aliasNames.get( name.toLowerCase() ) : name ) ) ;
    }
    
    /** 利用可能かチェック. **/
    protected static final boolean isUse( Config conf ) throws Exception {
        if( !conf.isSection( "database" ) ) {
            throw new MimdbException( "databaseセクションが存在しません" ) ;
        }
        return conf.getBoolean( "database","use",0 ) ;
    }
    
    /** 初期処理. **/
    private final void init( Config conf ) throws Exception {
        if( !conf.isSection( "database" ) ) {
            throw new MimdbException( "databaseセクションが存在しません" ) ;
        }
        
        // データベース名を取得.
        String nm = conf.getString( "database","name",0 ) ;
        if( nm == null || ( nm = nm.trim().toLowerCase() ).length() <= 0 ) {
            throw new MimdbException( "データベース名が設定されていません" ) ;
        }
        
        // 基本フォルダ指定を読み込む.
        String bs = conf.getString( "database","base",0 ) ;
        if( bs == null || ( bs = bs.trim() ).length() <= 0 ) {
            bs = null ;
        }
        
        // テーブル定義コンフィグファイル名を読み込む.
        int len = conf.size( "database","table" ) ;
        if( len <= 0 ) {
            throw new MimdbException( "テーブル定義が１つも行われていません" ) ;
        }
        String[] list = new String[ len ] ;
        for( int i = 0 ; i < len ; i ++ ) {
            list[ i ] = conf.getString( "database","table",i ) ;
            if( list[ i ] == null || ( list[ i ] = list[ i ].trim() ).length() <= 0 ) {
                throw new MimdbException( "[database]セクションのtable(" + (i+1) + ")が正しく設定されていません" ) ;
            }
            if( !list[ i ].toLowerCase().endsWith( ".conf" ) ) {
                list[ i ] += ".conf" ;
            }
        }
        if( bs == null ) {
            for( int i = 0 ; i < len ; i ++ ) {
                list[ i ] = MimdbUtils.getFullPath( list[ i ] ) ;
            }
        }
        else {
            bs = MimdbUtils.getFullPath( bs ) ;
            if( !bs.endsWith( "/" ) ) {
                bs += "/" ;
            }
            for( int i = 0 ; i < len ; i ++ ) {
                list[ i ] = MimdbUtils.getFullPath( bs + list[ i ] ) ;
            }
        }
        // 読み込んだテーブル定義コンフィグを基にテーブル作成.
        Map<String,String> map = new HashMap<String,String>() ;
        String[] tbls = new String[ len ] ;
        for( int i = 0 ; i < len ; i ++ ) {
            tbls[ i ] = MimdbTableInstance.create( bs,list[ i ] ) ;
            map.put( tbls[ i ],list[ i ] ) ;
        }
        
        // メイン定義.
        baseFolder = bs ;
        tableConfigList = map ;
        tableNames = tbls ;
        name = nm ;
    }
    
    /**
     * オブジェクトの破棄.
     */
    public void destroy() {
        conf = null ;
        baseFolder = null ;
        tableConfigList = null ;
        tableNames = null ;
        name = null ;
        
        MimdbTableManager.getInstance().destroy() ;
        useFlag = false ;
    }
    
    /**
     * オブジェクトが利用可能かチェック.
     * @return boolean [true]の場合、利用可能です.
     */
    public boolean isUse() {
        return useFlag ;
    }
    
    /**
     * データベース名を取得.
     * @return String データベース名が返却されます.
     */
    public String getName() {
        return name ;
    }
    
    /**
     * 基本フォルダー定義を取得.
     * @return String 基本フォルダー定義先が返却されます.
     */
    public String getBaseFolder() {
        sync.readLock().lock() ;
        try {
            return baseFolder ;
        } finally {
            sync.readLock().unlock() ;
        }
    }
    
    /**
     * 定義テーブル一覧を取得.
     * @return String[] 定義テーブル一覧が返却されます.
     */
    public String[] getTables() {
        sync.readLock().lock() ;
        try {
            return tableNames ;
        } finally {
            sync.readLock().unlock() ;
        }
    }
    
    /**
     * 定義テーブル数を取得.
     * @return int 定義テーブル数が返却されます.
     */
    public int length() {
        sync.readLock().lock() ;
        try {
            return tableNames.length ;
        } finally {
            sync.readLock().unlock() ;
        }
    }
    
    /**
     * 指定テーブルが存在するかチェック.
     * @param name 対象のテーブル名を設定します.
     * @return boolean [true]の場合、存在します.
     */
    public boolean isTable( String name ) {
        sync.readLock().lock() ;
        try {
            return tableConfigList.containsKey( name.trim().toLowerCase() ) ;
        } finally {
            sync.readLock().unlock() ;
        }
    }
    
    /**
     * 指定テーブルの再読み込み.
     * @param name 再読み込み対象のテーブル名を設定します.
     * @return boolean [true]の場合、正常に再読み込みできました.
     * @exception Exception 例外.
     */
    public boolean reload( String name ) throws Exception {
        sync.writeLock().lock() ;
        try {
            name = name.trim().toLowerCase() ;
            String file = tableConfigList.get( name ) ;
            if( file == null ) {
                return false ;
            }
            // 再読み込み.
            String res = MimdbTableInstance.create( baseFolder,file ) ;
            // 再読み込みしたコンフィグ条件のテーブル名が前回のテーブル名と
            // 一致しない場合は、現在のテーブルを削除.
            if( !name.equals( res ) ) {
                MimdbTableManager.getInstance().remove( name ) ;
                tableConfigList.remove( name ) ;
                tableConfigList.put( res,file ) ;
                int len = tableNames.length ;
                
                // 以前に登録されていたテーブル名と重複登録されている場合.
                if( tableConfigList.size() != len ) {
                    String[] lst = new String[ tableConfigList.size() ] ;
                    int cnt = 0 ;
                    Iterator<String> it = tableConfigList.keySet().iterator() ;
                    while( it.hasNext() ) {
                        lst[ cnt ++ ] = it.next() ;
                    }
                    tableNames = lst ;
                }
                // 以前に登録されていたテーブル名と重複していない場合.
                else {
                    for( int i = 0 ; i < len ; i ++ ) {
                        if( tableNames[ i ].equals( name ) ) {
                            tableNames[ i ] = res ;
                        }
                    }
                }
                
            }
            return true ;
        } finally {
            sync.writeLock().unlock() ;
        }
    }
    
    /**
     * 全テーブル情報を再読み込み.
     * @exception Exception 例外.
     */
    public void reloadAll() throws Exception {
        sync.writeLock().lock() ;
        try {
            String[] bk = tableNames ;
            // 初期処理で全再読み込み.
            init( conf ) ;
            // テーブル名の状況をチェック.
            // 変更テーブル名で、前回のテーブル名が存在しない場合は、そのテーブルを削除.
            int len = bk.length ;
            MimdbTableManager man = MimdbTableManager.getInstance() ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( !tableConfigList.containsKey( bk[ i ] ) ) {
                    man.remove( bk[ i ] ) ;
                }
            }
        } finally {
            sync.writeLock().unlock() ;
        }
    }
    
    /**
     * 全データーを再取得.
     * @exception Exception 例外.
     */
    public void loadAll() throws Exception {
        sync.writeLock().lock() ;
        try {
            // 設定条件(Conf)が、file経由でのロードで無い場合はエラー.
            String file = conf.getSrcName() ;
            if( file == null ) {
                throw new IOException( "MimdbEmbeddedの読み込みはファイル経由では無いので、再読み込みできません" ) ;
            }
            
            // 新しいコンフィグ条件をセット.
            Config bk = conf ;
            try {
                conf = Config.read( file ) ;
                // テーブルを全部更新.
                reloadAll() ;
            } catch( Exception e ) {
                // 読み込み例外が発生した場合は、一旦管理内容を全削除.
                // ただしこの例外が発生した場合は、updateIDが引き付けない.
                // ※後に対応を考える.
                MimdbTableManager.getInstance().destroy() ;
                conf = bk ;
                reloadAll() ;
                throw e ;
            }
            
        } finally {
            sync.writeLock().unlock() ;
        }
    }
    
    /**
     * srcNameを取得.
     * @return String srcNameが返却されます.
     *                ファイル経由で読み込まれていない場合は[null]が返却されます.
     */
    public String getSrcName() {
        return conf.getSrcName() ;
    }
}

