package org.maachang.dbm.service ;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.connector.ConnectorSession;
import org.maachang.dbm.MDbm;
import org.maachang.dbm.MDbmManager;
import org.maachang.util.ConvertParam;


/**
 * MDBM実行.
 * 
 * @version 2008/01/18
 * @author masahito suzuki
 * @since MaachangDBM 1.03
 */
class ExecutionMDbm {
    
    /**
     * ログ出力.
     */
    private static final Log LOG = LogFactory.getLog( ExecutionMDbm.class ) ;
    
    /**
     * １度に送信するKey数.
     */
    private static final int ONE_SEND_KEY = 48 ;
    
    /**
     * セッション実行.
     */
    public static final void execution( MDbm mdbm,KeySessionManager manager,byte[] bin,ConnectorSession session )
        throws Exception {
        if( session == null || session.isClosed() == true ) {
            return ;
        }
        if( MDbmManager.getInstance().isClose() == true ) {
            returnError( session,"MDbmマネージャは停止しています" ) ;
            return ;
        }
        try {
            int type = getSendType( bin ) ;
            // トランザクションMDBMを取得.
            mdbm = getTransaction( manager,mdbm,type,bin ) ;
            switch( type ) {
                case ProtocolDef.SEND_CLOSE : resultClose( manager,mdbm,bin,session ) ; return ;
                case ProtocolDef.SEND_COMMIT : resultCommit( manager,mdbm,bin,session ) ; return ;
                case ProtocolDef.SEND_ROLLBACK : resultRollback( manager,mdbm,bin,session ) ; return ;
                case ProtocolDef.SEND_TRANSACTOIN : resultCheck( mdbm,session ) ; return ;
                case ProtocolDef.SEND_PUT : resultPut( mdbm,bin,session ) ; return ;
                case ProtocolDef.SEND_REMOVE : resultRemove( mdbm,bin,session ) ; return ;
                case ProtocolDef.SEND_GET : resultGet( mdbm,bin,session ) ; return ;
                case ProtocolDef.SEND_CONTAINS : resultContains( mdbm,bin,session ) ; return ;
                case ProtocolDef.SEND_SIZE : resulSize( mdbm,session ) ; return ;
                case ProtocolDef.SEND_DIRECTORY : resultDirectory( mdbm,session ) ; return ;
                case ProtocolDef.SEND_INIT_KEY : resulInitKey( manager,mdbm,bin,session ) ; return ;
                case ProtocolDef.SEND_HAS_KEY : resulHasKey( manager,mdbm,bin,session ) ; return ;
                case ProtocolDef.SEND_GET_KEY : resulGetKey( manager,mdbm,bin,session ) ; return ;
                case ProtocolDef.SEND_SEQUENCE_ID : resultSequenceId( mdbm,bin,session ) ; return ;
                case ProtocolDef.SEND_SESSION_ID : resultSessionId( mdbm,session ) ; return ;
            }
            returnError( session,"error:不明なプロトコル["+type+"]です" ) ;
        } catch( Exception e ) {
            LOG.warn( "## 通信例外",e ) ;
            try {
                returnError( session,"error:"+e.getLocalizedMessage() ) ;
            } catch( Exception ee ) {
                if( session != null ) {
                    session.destroy() ;
                }
            }
        }
    }
    
    /**
     * 命令タイプ取得.
     */
    private static final int getSendType( byte[] bin )
        throws Exception {
        return ConvertParam.convertInt( ProtocolDef.OFFSET,bin ) ;
    }
    
    /**
     * クローズ実行.
     */
    private static final void resultClose( KeySessionManager manager,MDbm mdbm,byte[] bin,ConnectorSession session ) throws Exception {
        int pnt = ProtocolDef.SEND_OFFSET ;
        long sessionId = ConvertParam.convertLong( pnt,bin ) ;
        manager.remove( sessionId ) ;
        mdbm.close() ;
        session.destroy() ;
    }
    
    /**
     * コミット実行.
     */
    private static final void resultCommit( KeySessionManager manager,MDbm mdbm,byte[] bin,ConnectorSession session ) throws Exception {
        int pnt = ProtocolDef.SEND_OFFSET ;
        long sessionId = ConvertParam.convertLong( pnt,bin ) ;
        mdbm.commit() ;
        manager.removeKey( sessionId ) ;
        returnSuccess( session ) ;
    }
    
    /**
     * ロールバック実行.
     */
    private static final void resultRollback( KeySessionManager manager,MDbm mdbm,byte[] bin,ConnectorSession session ) throws Exception {
        int pnt = ProtocolDef.SEND_OFFSET ;
        long sessionId = ConvertParam.convertLong( pnt,bin ) ;
        pnt += 8 ;
        manager.removeKey( sessionId ) ;
        mdbm.rollback() ;
        returnSuccess( session ) ;
    }
    
    /**
     * トランザクションチェック実行.
     */
    private static final void resultCheck( MDbm mdbm,ConnectorSession session ) throws Exception {
        mdbm.check() ;
        returnSuccess( session ) ;
    }
    
    /**
     * データPut実行.
     */
    private static final void resultPut( MDbm mdbm,byte[] bin,ConnectorSession session ) throws Exception {
        int pnt = ProtocolDef.SEND_OFFSET + 8 ;
        int keyLen = ConvertParam.convertInt( pnt,bin ) ;
        pnt += 4 ;
        byte[] key = new byte[ keyLen ] ;
        System.arraycopy( bin,pnt,key,0,keyLen ) ;
        pnt += keyLen ;
        int valLen = ConvertParam.convertInt( pnt,bin ) ;
        pnt += 4 ;
        byte[] value = new byte[ valLen ] ;
        System.arraycopy( bin,pnt,value,0,valLen ) ;
        mdbm.put( key,value ) ;
        key = null ;
        value = null ;
        returnSuccess( session ) ;
    }
    
    /**
     * データRemove実行.
     */
    private static final void resultRemove( MDbm mdbm,byte[] bin,ConnectorSession session ) throws Exception {
        int pnt = ProtocolDef.SEND_OFFSET + 8 ;
        int keyLen = ConvertParam.convertInt( pnt,bin ) ;
        pnt += 4 ;
        byte[] key = new byte[ keyLen ] ;
        System.arraycopy( bin,pnt,key,0,keyLen ) ;
        mdbm.remove( key ) ;
        key = null ;
        returnSuccess( session ) ;
    }
    
    /**
     * データGet実行.
     */
    private static final void resultGet( MDbm mdbm,byte[] bin,ConnectorSession session ) throws Exception {
        int pnt = ProtocolDef.SEND_OFFSET + 8 ;
        int keyLen = ConvertParam.convertInt( pnt,bin ) ;
        pnt += 4 ;
        byte[] key = new byte[ keyLen ] ;
        System.arraycopy( bin,pnt,key,0,keyLen ) ;
        byte[] ret = mdbm.get( key ) ;
        key = null ;
        returnData( session,ret ) ;
    }
    
    /**
     * データContails実行.
     */
    private static final void resultContains( MDbm mdbm,byte[] bin,ConnectorSession session ) throws Exception {
        int pnt = ProtocolDef.SEND_OFFSET + 8 ;
        int keyLen = ConvertParam.convertInt( pnt,bin ) ;
        pnt += 4 ;
        byte[] key = new byte[ keyLen ] ;
        System.arraycopy( bin,pnt,key,0,keyLen ) ;
        boolean ret = mdbm.containsKey( key ) ;
        key = null ;
        returnBool( session,ret ) ;
    }
    
    /**
     * データSize実行.
     */
    private static final void resulSize( MDbm mdbm,ConnectorSession session ) throws Exception {
        int ret = mdbm.size() ;
        returnSize( session,ret ) ;
    }
    
    /**
     * データDirectory実行.
     */
    private static final void resultDirectory( MDbm mdbm,ConnectorSession session ) throws Exception {
        String ret = mdbm.getDirectory() ;
        if( ret == null || ret.length() <= 0 ) {
            returnData( session,null ) ;
        }
        else {
            returnData( session,ret.getBytes( "UTF8" ) ) ;
        }
    }
    
    /**
     * Key一覧初期化実行.
     */
    private static final void resulInitKey( KeySessionManager manager,MDbm mdbm,byte[] bin,ConnectorSession session ) throws Exception {
        int pnt = ProtocolDef.SEND_OFFSET ;
        long sessionId = ConvertParam.convertLong( pnt,bin ) ;
        manager.removeKey( sessionId ) ;
        returnSuccess( session ) ;
    }
    
    /**
     * Key一覧確認実行.
     */
    private static final void resulHasKey( KeySessionManager manager,MDbm mdbm,byte[] bin,ConnectorSession session ) throws Exception {
        int pnt = ProtocolDef.SEND_OFFSET ;
        long sessionId = ConvertParam.convertLong( pnt,bin ) ;
        Enumeration<byte[]> keys = manager.getKey( sessionId ) ;
        if( keys == null ) {
            keys = mdbm.elements() ;
            manager.putKey( sessionId,keys ) ;
        }
        boolean ret = keys.hasMoreElements() ;
        returnBool( session,ret ) ;
    }
    
    /**
     * Key一覧確認実行.
     */
    private static final void resulGetKey( KeySessionManager manager,MDbm mdbm,byte[] bin,ConnectorSession session ) throws Exception {
        int pnt = ProtocolDef.SEND_OFFSET ;
        long sessionId = ConvertParam.convertLong( pnt,bin ) ;
        Enumeration<byte[]> keys = manager.getKey( sessionId ) ;
        if( keys == null ) {
            keys = mdbm.elements() ;
            manager.putKey( sessionId,keys ) ;
        }
        ArrayList<byte[]> ret = new ArrayList<byte[]>() ;
        for( int i = 0 ; i < ONE_SEND_KEY ; i ++ ) {
            byte[] b = keys.nextElement() ;
            if( b == null ) {
                break ;
            }
            ret.add( b ) ;
        }
        returnKey( session,ret ) ;
    }
    
    /**
     * シーケンスID実行.
     */
    private static final void resultSequenceId( MDbm mdbm,byte[] bin,ConnectorSession session ) throws Exception {
        int pnt = ProtocolDef.SEND_OFFSET + 8 ;
        int no = ConvertParam.convertInt( pnt,bin ) ;
        long ret = -1L ;
        if( no >= 0 && no < ProtocolDef.SESSION_ID_SEQ_NO ) {
            ret = mdbm.sequenceId( no ) ;
        }
        returnId( session,ret ) ;
    }
    
    /**
     * セッションID実行.
     */
    private static final void resultSessionId( MDbm mdbm,ConnectorSession session ) throws Exception {
        returnId( session,getSessionId( mdbm ) ) ;
    }
    
    
    
    /**
     * 正常結果を返す.
     */
    private static final void returnSuccess( ConnectorSession session ) throws Exception {
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.RESULT_SUCCESS ) ) ;
        send( session,o ) ;
    }
    
    /**
     * 異常結果を返す.
     */
    private static final void returnError( ConnectorSession session,String message ) throws Exception {
        if( message == null || ( message = message.trim() ).length() <= 0 ) {
            message = "エラーが発生しました" ;
        }
        OutputStream o = getOutputStream() ;
        byte[] b = message.getBytes( "UTF8" ) ;
        o.write( ConvertParam.convertInt( ProtocolDef.RESULT_ERROR ) ) ;
        o.write( ConvertParam.convertInt( b.length ) ) ;
        o.write( b ) ;
        send( session,o ) ;
    }
    
    /**
     * 存在確認結果を返す.
     */
    private static final void returnBool( ConnectorSession session,boolean bool ) throws Exception {
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.RESULT_BOOL ) ) ;
        o.write( ConvertParam.convertBoolean( bool ) ) ;
        send( session,o ) ;
    }
    
    /**
     * データ取得結果を返す.
     */
    private static final void returnData( ConnectorSession session,byte[] data ) throws Exception {
        int len = 0 ;
        if( data != null ) {
            len = data.length ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.RESULT_DATA ) ) ;
        o.write( ConvertParam.convertInt( len ) ) ;
        if( len > 0 ) {
            o.write( data ) ;
        }
        send( session,o ) ;
    }
    
    /**
     * Keyデータを返す.
     */
    private static final void returnKey( ConnectorSession session,ArrayList<byte[]> data ) throws Exception {
        int len = 0 ;
        if( data != null ) {
            len = data.size() ;
        }
        int next = 0 ;
        for( int i = 0 ; i < len ; i ++ ) {
            byte[] b = data.get( i ) ;
            next = next + ( b.length + 4 ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.RESULT_KEY ) ) ;
        o.write( ConvertParam.convertInt( len ) ) ;
        for( int i = 0 ; i < len ; i ++ ) {
            byte[] b = data.get( i ) ;
            o.write( ConvertParam.convertInt( b.length ) ) ;
            o.write( b ) ;
        }
        send( session,o ) ;
    }
    
    /**
     * データ取得結果を返す.
     */
    private static final void returnSize( ConnectorSession session,int size ) throws Exception {
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.RESULT_SIZE ) ) ;
        o.write( ConvertParam.convertInt( size ) ) ;
        send( session,o ) ;
    }
    
    /**
     * ID取得結果を返す.
     */
    private static final void returnId( ConnectorSession session,long id ) throws Exception {
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.RESULT_ID ) ) ;
        o.write( ConvertParam.convertLong( id ) ) ;
        send( session,o ) ;
    }
    
    /**
     * 書き込みデータを取得.
     */
    private static final OutputStream getOutputStream() throws Exception {
        return new ByteArrayOutputStream() ;
    }
    
    /**
     * 書き込みデータを送信.
     */
    private static final void send( ConnectorSession session,OutputStream data )
        throws Exception {
        byte[] b = ( ( ByteArrayOutputStream )data ).toByteArray() ;
        session.send( b ) ;
    }
    
    /**
     * セッションIDを取得.
     */
    private static final long getSessionId( MDbm mdbm ) throws Exception {
        long ret = mdbm.sequenceId( ProtocolDef.SESSION_ID_SEQ_NO ) ;
        if( ret == 0 ) {
            ret = mdbm.sequenceId( ProtocolDef.SESSION_ID_SEQ_NO ) ;
        }
        return ret ;
    }
    
    /**
     * 指定タイプからトランザクションMDBMを取得.
     */
    private static final MDbm getTransaction( KeySessionManager manager,MDbm mdbm,int type,byte[] bin )
        throws Exception {
        if( mdbm == null || mdbm.isUse() == false ) {
            throw new IOException( "MDBMは、利用できません" ) ;
        }
        switch( type ) {
            case ProtocolDef.SEND_CLOSE :
            case ProtocolDef.SEND_COMMIT :
            case ProtocolDef.SEND_ROLLBACK :
            case ProtocolDef.SEND_TRANSACTOIN :
            case ProtocolDef.SEND_PUT :
            case ProtocolDef.SEND_REMOVE :
            case ProtocolDef.SEND_GET :
            case ProtocolDef.SEND_CONTAINS :
            case ProtocolDef.SEND_SIZE :
            case ProtocolDef.SEND_DIRECTORY :
            case ProtocolDef.SEND_INIT_KEY :
            case ProtocolDef.SEND_HAS_KEY :
            case ProtocolDef.SEND_GET_KEY :
            case ProtocolDef.SEND_SEQUENCE_ID :
            {
                long seq = ConvertParam.convertLong( ProtocolDef.SEND_OFFSET,bin ) ;
                if( seq <= 0 ) {
                    return mdbm ;
                }
                MDbm ret = manager.getMDbm( seq ) ;
                if( ret == null ) {
                    ret = MDbmManager.getInstance().getTransaction( mdbm ) ;
                    manager.putMDbm( seq,ret ) ;
                }
                return ret ;
            }
        }
        return mdbm ;
    }
}
