package org.maachang.mimdb ;

import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.maachang.mimdb.core.impl.MimdbUtils;
import org.maachang.mimdb.core.util.AtomicNumber32;
import org.maachang.mimdb.core.util.Config;
import org.maachang.mimdb.server.MimdbMaintenanceServer;

/**
 * Mimdbメイン.
 * 
 * @version 2013/11/07
 * @author masahito suzuki
 * @since MasterInMemDB 1.00
 */
public final class Mimdb {
    private Mimdb() {}
    
    /** メジャーバージョン. **/
    public static final int VERSION_MAJOR = 1 ;
    
    /** マイナーバージョン. **/
    public static final int VERSION_MINOR = 0 ;
    
    /** バージョン文字列. **/
    public static final String VERSION = VERSION_MAJOR + "." + VERSION_MINOR ;
    
    /** オブジェクト管理. **/
    protected static final Mimdb SNGL = new Mimdb() ;
    
    
    /** メンテナンスサーバー. **/
    private MimdbMaintenanceServer maintenance = null ;
    
    /** 初期処理フラグ. **/
    private final AtomicNumber32 initFlag = new AtomicNumber32( 0 ) ;
    
    /** データベース管理. **/
    protected Map<String,MimdbDatabase> manager = new ConcurrentHashMap<String,MimdbDatabase>() ;
    
    /** 別名管理. **/
    protected Map<String,String> aliasNames = new ConcurrentHashMap<String,String>() ;
    
    /**
     * ReadWriteロック用オブジェクト.
     */
    private ReentrantReadWriteLock sync = new ReentrantReadWriteLock() ;
    
    
    
    /**
     * オブジェクトを取得.
     * @return Mimdb オブジェクトが返却されます.
     */
    public static final Mimdb getInstance() {
        return SNGL ;
    }
    
    /**
     * 初期処理.
     * @param name 対象のコンフィグファイル名を設定します.
     * @exception Exception 例外.
     */
    public final void init( String name ) throws Exception {
        init( Config.read( name ) ) ;
    }
    
    /**
     * 初期処理.
     * @param conf 対象のコンフィグ情報を設定します.
     * @exception Exception 例外.
     */
    public final void init( Config conf ) throws Exception {
        int old = -1 ;
        sync.writeLock().lock() ;
        try {
            if( ( old = initFlag.setToBeforeReturn( 1 ) ) != 0 ) {
                throw new MimdbException( "既に初期処理は行われています" ) ;
            }
            if( !conf.isSection( "mimdb" ) ) {
                throw new MimdbException( "mimdbセクションが存在しません" ) ;
            }
            if( !conf.isSection( "maintenance" ) ) {
                throw new MimdbException( "maintenanceセクションが存在しません" ) ;
            }
            
            // 別名定義の処理.
            initMimdb( conf ) ;
            
            // メンテナンス初期化処理.
            initMaintenance( conf ) ;
            
        } catch( Exception e ) {
            if( old != -1 ) {
                initFlag.set( old ) ;
            }
            throw e ;
        } finally {
            sync.writeLock().unlock() ;
        }
    }
    
    /** 別名定義情報を取得. **/
    private final void initMimdb( Config conf ) throws Exception {
        String[] keys = conf.getKeys( "mimdb" ) ;
        if( keys == null || keys.length <= 0 ) {
            aliasNames.clear() ;
            return ;
        }
        String s ;
        int len = keys.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            keys[ i ] = keys[ i ].trim().toLowerCase() ;
            if( ( s = conf.get( "mimdb",keys[ i ],0 ) ) == null || ( s = s.trim() ).length() <= 0 ) {
                continue ;
            }
            aliasNames.put( keys[ i ],MimdbUtils.getFullPath( s ) ) ;
        }
    }
    
    /** メンテナンス初期処理. **/
    private final void initMaintenance( Config conf ) throws Exception {
        if( !conf.getBoolean( "maintenance","mode",0 ) ) {
            maintenance = null ;
            return ;
        }
        String addr = conf.getString( "maintenance","addr",0 ) ;
        if( addr == null || ( addr = addr.trim() ).length() <= 0 ) {
            addr = null ;
        }
        // メンテナンスオブジェクトの生成.
        maintenance = new MimdbMaintenanceServer( manager,
            addr,
            conf.getInt( "maintenance","port",0 ),
            conf.getInt( "maintenance","backlog",0 ) ) ;
    }
    
    /** 初期化チェック. **/
    private final void check() throws Exception {
        if( initFlag.get() != 1 ) {
            throw new MimdbException( "Mimdb初期化処理が行われていません" ) ;
        }
    }
    
    /**
     * 初期処理が完了しているかチェック.
     * @return boolean [true]の場合は初期処理は完了しています.
     */
    public boolean isInit() {
        return initFlag.get() == 1 ;
    }
    
    /**
     * メンテナンスサーバーが起動しているかチェック.
     * @return boolean [true]の場合、起動中です.
     */
    public boolean isMaintenance() {
        sync.readLock().lock() ;
        try {
            return ( maintenance != null ) ;
        } finally {
            sync.readLock().unlock() ;
        }
    }
    
    /**
     * メンテナンスサーバースレッドが動作中かチェック.
     * @return boolean [true]の場合、スレッドは実行中です.
     */
    public boolean isMaintenanceThread() {
        sync.readLock().lock() ;
        try {
            return ( maintenance != null && maintenance.isStart() ) ;
        } finally {
            sync.readLock().unlock() ;
        }
    }
    
    /**
     * alias定義されているデータベースを全てロード.
     * @exception Exception 例外.
     */
    public void loadAll() throws Exception {
        Iterator<String> it = aliasNames.keySet().iterator() ;
        while( it.hasNext() ) {
            getDatabase( it.next() ) ;
        }
    }
    
    /**
     * データベースの登録／取得.
     * @param name 対象のデータベース名を設定します.
     * @return MimdbDatabase 対象のデータベース情報が返却されます.
     * @exception Exception 例外.
     */
    public MimdbDatabase getDatabase( String name ) throws Exception {
        check() ;
        String base = name ;
        name = name.trim() ;
        String n = name.toLowerCase() ;
        
        // 別名定義に一致する場合は、パス変換.
        if( aliasNames.containsKey( n ) ) {
            name = aliasNames.get( n ) ;
        }
        // コンフィグファイル名の拡張子が存在しない場合はセット.
        if( !name.toLowerCase().endsWith( ".conf" ) ) {
            name += ".conf" ;
        }
        name = MimdbUtils.getFullPath( name ) ;
        MimdbDatabase em = manager.get( name ) ;
        
        // 新規呼び出しの場合.
        if( em == null ) {
            // 2重呼び出し防止.
            sync.writeLock().lock() ;
            try {
                if( ( em = manager.get( name ) ) == null ) {
                    if( MimdbDatabase.isUse( name ) ) {
                        em = new MimdbDatabase( name ) ;
                        manager.put( name,em ) ;
                    }
                    else {
                        throw new MimdbException(
                            "指定データベース定義先[" + base +
                            "]は無効定義されています" ) ;
                    }
                }
            } finally {
                sync.writeLock().unlock() ;
            }
        }
        return em ;
    }
    
    /**
     * データベース管理テーブルの取得.
     * @return Map<String,MimdbDatabase> データベース管理テーブルが返却されます.
     * @exception Exception 例外.
     */
    public Map<String,MimdbDatabase> getManager()
        throws Exception {
        check() ;
        return manager ;
    }
    
    /**
     * 別名管理テーブルの取得.
     * @return Map<String,String> 別名テーブルが返却されます.
     * @exception Exception 例外.
     */
    public Map<String,String> getAlias()
        throws Exception {
        check() ;
        return aliasNames ;
    }
}

