package org.maachang.dbm.service.client ;

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

import org.maachang.connector.ClientConnector;
import org.maachang.dbm.service.ProtocolDef;
import org.maachang.util.ConvertParam;

/**
 * MaachangDbmクライアントコネクション
 * 
 * @version 2008/01/20
 * @author masahito suzuki
 * @since MaachangDBM 1.00
 */
class MDbmClientConnection {
    
    private static final String NOT_ACCESS = "コネクション接続が確立していません" ;
    private ClientConnector conns = null ;
    private long sessionId = -1L ;
    
    /**
     * コンストラクタ.
     */
    private MDbmClientConnection() {
        
    }
    
    /**
     * コンストラクタ.
     */
    public MDbmClientConnection( ClientConnector conns )
        throws Exception {
        if( conns == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        long ss = readSessionId( conns ) ;
        if( ss <= 0 ) {
            throw new IOException( "セッションIDの取得に失敗しました" ) ;
        }
        this.conns = conns ;
        this.sessionId = ss ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.destroy() ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public void destroy() {
        try {
            close() ;
        } catch( Exception e ) {
        }
    }
    
    /**
     * クローズ処理.
     */
    public void close() throws Exception {
        if( isUse() == false ) {
            throw new IOException( NOT_ACCESS ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_CLOSE ) ) ;
        o.write( ConvertParam.convertLong( sessionId ) ) ;
        sendRecv( conns,o ) ;
        o = null ;
        if( conns != null ) {
            conns.close() ;
        }
    }
    
    /**
     * クライアントコネクタを取得.
     */
    public ClientConnector getConnector() {
        return conns ;
    }
    
    /**
     * コミット処理.
     */
    public void commit() throws Exception {
        if( isUse() == false ) {
            throw new IOException( NOT_ACCESS ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_COMMIT ) ) ;
        o.write( ConvertParam.convertLong( sessionId ) ) ;
        byte[] b = sendRecv( conns,o ) ;
        o = null ;
        if( resultType( b ) != ProtocolDef.RESULT_SUCCESS ) {
            throw new IOException( "[commit]結果プロトコルは不正です" ) ;
        }
    }
    
    /**
     * ロールバック処理.
     */
    public void rollback() throws Exception {
        if( isUse() == false ) {
            throw new IOException( NOT_ACCESS ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_ROLLBACK ) ) ;
        o.write( ConvertParam.convertLong( sessionId ) ) ;
        byte[] b = sendRecv( conns,o ) ;
        o = null ;
        if( resultType( b ) != ProtocolDef.RESULT_SUCCESS ) {
            throw new IOException( "[rollback]結果プロトコルは不正です" ) ;
        }
    }
    
    /**
     * コミット／ロールバック可能かチェック.
     */
    public void check() throws Exception {
        if( isUse() == false ) {
            throw new IOException( NOT_ACCESS ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_TRANSACTOIN ) ) ;
        o.write( ConvertParam.convertLong( sessionId ) ) ;
        byte[] b = sendRecv( conns,o ) ;
        o = null ;
        if( resultType( b ) != ProtocolDef.RESULT_SUCCESS ) {
            throw new IOException( "[check]結果プロトコルは不正です" ) ;
        }
    }
    
    /**
     * データセット.
     */
    public void put( byte[] key,byte[] value ) throws Exception {
        if( key == null || key.length <= 0 ) {
            throw new IllegalArgumentException( "指定キーは不正です" ) ;
        }
        if( value == null || value.length <= 0 ) {
            throw new IllegalArgumentException( "指定要素は不正です" ) ;
        }
        if( isUse() == false ) {
            throw new IOException( NOT_ACCESS ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_PUT ) ) ;
        o.write( ConvertParam.convertLong( sessionId ) ) ;
        o.write( ConvertParam.convertInt( key.length ) ) ;
        o.write( key ) ;
        o.write( ConvertParam.convertInt( value.length ) ) ;
        o.write( value ) ;
        byte[] b = sendRecv( conns,o ) ;
        o = null ;
        if( resultType( b ) != ProtocolDef.RESULT_SUCCESS ) {
            throw new IOException( "[put]結果プロトコルは不正です" ) ;
        }
    }
    
    /**
     * データ削除.
     */
    public void remove( byte[] key ) throws Exception {
        if( key == null || key.length <= 0 ) {
            throw new IllegalArgumentException( "指定キーは不正です" ) ;
        }
        if( isUse() == false ) {
            throw new IOException( NOT_ACCESS ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_REMOVE ) ) ;
        o.write( ConvertParam.convertLong( sessionId ) ) ;
        o.write( ConvertParam.convertInt( key.length ) ) ;
        o.write( key ) ;
        byte[] b = sendRecv( conns,o ) ;
        o = null ;
        if( resultType( b ) != ProtocolDef.RESULT_SUCCESS ) {
            throw new IOException( "[remove]結果プロトコルは不正です" ) ;
        }
    }
    
    /**
     * データ取得.
     */
    public byte[] get( byte[] key ) throws Exception {
        if( key == null || key.length <= 0 ) {
            throw new IllegalArgumentException( "指定キーは不正です" ) ;
        }
        if( isUse() == false ) {
            throw new IOException( NOT_ACCESS ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_GET ) ) ;
        o.write( ConvertParam.convertLong( sessionId ) ) ;
        o.write( ConvertParam.convertInt( key.length ) ) ;
        o.write( key ) ;
        byte[] b = sendRecv( conns,o ) ;
        o = null ;
        if( resultType( b ) != ProtocolDef.RESULT_DATA ) {
            throw new IOException( "[get]結果プロトコルは不正です" ) ;
        }
        return dataResult( b ) ;
    }
    
    /**
     * データ存在チェック.
     */
    public boolean containsKey( byte[] key) throws Exception {
        if( key == null || key.length <= 0 ) {
            throw new IllegalArgumentException( "指定キーは不正です" ) ;
        }
        if( isUse() == false ) {
            throw new IOException( NOT_ACCESS ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_CONTAINS ) ) ;
        o.write( ConvertParam.convertLong( sessionId ) ) ;
        o.write( ConvertParam.convertInt( key.length ) ) ;
        o.write( key ) ;
        byte[] b = sendRecv( conns,o ) ;
        o = null ;
        if( resultType( b ) != ProtocolDef.RESULT_BOOL ) {
            throw new IOException( "[containsKey]結果プロトコルは不正です" ) ;
        }
        return booleanResult( b ) ;
    }
    
    /**
     * データ数取得.
     */
    public int size() throws Exception {
        if( isUse() == false ) {
            throw new IOException( NOT_ACCESS ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_SIZE ) ) ;
        o.write( ConvertParam.convertLong( sessionId ) ) ;
        byte[] b = sendRecv( conns,o ) ;
        o = null ;
        if( resultType( b ) != ProtocolDef.RESULT_SIZE ) {
            throw new IOException( "[size]結果プロトコルは不正です" ) ;
        }
        return sizeResult( b ) ;
    }
    
    /**
     * ディレクトリ名取得.
     */
    public String getDirectory() throws Exception {
        if( isUse() == false ) {
            throw new IOException( NOT_ACCESS ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_DIRECTORY ) ) ;
        o.write( ConvertParam.convertLong( sessionId ) ) ;
        byte[] b = sendRecv( conns,o ) ;
        o = null ;
        if( resultType( b ) != ProtocolDef.RESULT_DATA ) {
            throw new IOException( "[getDirectory]結果プロトコルは不正です" ) ;
        }
        byte[] res = dataResult( b ) ;
        if( res == null ) {
            return null ;
        }
        return new String( res,"UTF8" ) ;
    }
    
    /**
     * キー取得初期化.
     */
    public void initKey() throws Exception {
        if( isUse() == false ) {
            throw new IOException( NOT_ACCESS ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_INIT_KEY ) ) ;
        o.write( ConvertParam.convertLong( sessionId ) ) ;
        byte[] b = sendRecv( conns,o ) ;
        o = null ;
        if( resultType( b ) != ProtocolDef.RESULT_SUCCESS ) {
            throw new IOException( "[initKey]結果プロトコルは不正です" ) ;
        }
    }
    
    /**
     * 次のキーが存在するか確認.
     */
    public boolean hasKey() throws Exception {
        if( isUse() == false ) {
            throw new IOException( NOT_ACCESS ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_HAS_KEY ) ) ;
        o.write( ConvertParam.convertLong( sessionId ) ) ;
        byte[] b = sendRecv( conns,o ) ;
        o = null ;
        if( resultType( b ) != ProtocolDef.RESULT_BOOL ) {
            throw new IOException( "[hasKey]結果プロトコルは不正です" ) ;
        }
        return booleanResult( b ) ;
    }
    
    /**
     * 次のキー情報を取得.
     */
    public ArrayList<byte[]> getKey() throws Exception {
        if( isUse() == false ) {
            throw new IOException( NOT_ACCESS ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_GET_KEY ) ) ;
        o.write( ConvertParam.convertLong( sessionId ) ) ;
        byte[] b = sendRecv( conns,o ) ;
        o = null ;
        if( resultType( b ) != ProtocolDef.RESULT_KEY ) {
            throw new IOException( "[getKey]結果プロトコルは不正です" ) ;
        }
        return dataResultKey( b ) ;
    }
    
    /**
     * シーケンスIDを取得.
     */
    public long sequenceId( int no ) throws Exception {
        if( isUse() == false ) {
            throw new IOException( NOT_ACCESS ) ;
        }
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_SEQUENCE_ID ) ) ;
        o.write( ConvertParam.convertLong( sessionId ) ) ;
        o.write( ConvertParam.convertInt( no ) ) ;
        byte[] b = sendRecv( conns,o ) ;
        o = null ;
        if( resultType( b ) != ProtocolDef.RESULT_ID ) {
            throw new IOException( "[sequenceId]結果プロトコルは不正です" ) ;
        }
        return idResult( b ) ;
    }
    
    /**
     * セッションIDを取得.
     */
    private long readSessionId( ClientConnector conns )
        throws Exception {
        OutputStream o = getOutputStream() ;
        o.write( ConvertParam.convertInt( ProtocolDef.SEND_SESSION_ID ) ) ;
        byte[] b = sendRecv( conns,o ) ;
        o = null ;
        if( resultType( b ) != ProtocolDef.RESULT_ID ) {
            throw new IOException( "[sessionId]結果プロトコルは不正です" ) ;
        }
        return idResult( b ) ;
    }
    
    public boolean isUse() {
        boolean ret = true ;
        try {
            if( conns == null || conns.isClosed() == true ) {
                ret = false ;
            }
        } catch( Exception e ) {
            ret = false ;
        }
        return ret ;
    }
    
    private static final int getType( byte[] bin )
        throws Exception {
        if( bin == null || bin.length < ProtocolDef.SEND_OFFSET ) {
            throw new IOException( "不正なプロトコルです" ) ;
        }
        return ConvertParam.convertInt( ProtocolDef.OFFSET,bin ) ;
    }
    
    private static final int resultType( byte[] bin ) throws Exception {
        int type = getType( bin ) ;
        switch( type ) {
            case ProtocolDef.RESULT_SUCCESS : return ProtocolDef.RESULT_SUCCESS ;
            case ProtocolDef.RESULT_ERROR : throw new IOException( errorResult( bin ) ) ;
            case ProtocolDef.RESULT_BOOL : return ProtocolDef.RESULT_BOOL ;
            case ProtocolDef.RESULT_DATA : return ProtocolDef.RESULT_DATA ;
            case ProtocolDef.RESULT_KEY : return ProtocolDef.RESULT_KEY ;
            case ProtocolDef.RESULT_SIZE : return ProtocolDef.RESULT_SIZE ;
            case ProtocolDef.RESULT_ID : return ProtocolDef.RESULT_ID ;
        }
        throw new IOException( "不正な条件("+type+")が返されました" ) ;
    }
    
    private static final String errorResult( byte[] bin ) throws Exception {
        int len = ConvertParam.convertInt( ProtocolDef.SEND_OFFSET,bin ) ;
        int all = ProtocolDef.SEND_OFFSET + 4 + len ;
        if( all != bin.length ) {
            throw new IOException( "[error]プロトコル内容は不正です("+
                all+"/"+bin.length+")" ) ;
        }
        byte[] b = new byte[ len ] ;
        System.arraycopy( bin,ProtocolDef.SEND_OFFSET+4,b,0,len ) ;
        return new String( b,"UTF8" ) ;
    }
    
    private static final boolean booleanResult( byte[] bin ) throws Exception {
        int all = ProtocolDef.SEND_OFFSET + 1 ;
        if( all != bin.length ) {
            throw new IOException( "[boolean]プロトコル内容は不正です("+
                all+"/"+bin.length+")" ) ;
        }
        return ConvertParam.convertBoolean( ProtocolDef.SEND_OFFSET,bin ) ;
    }
    
    private static final int sizeResult( byte[] bin ) throws Exception {
        int all = ProtocolDef.SEND_OFFSET + 4 ;
        if( all != bin.length ) {
            throw new IOException( "[int]プロトコル内容は不正です("+
                all+"/"+bin.length+")" ) ;
        }
        return ConvertParam.convertInt( ProtocolDef.SEND_OFFSET,bin ) ;
    }
    
    private static final long idResult( byte[] bin ) throws Exception {
        int all = ProtocolDef.SEND_OFFSET + 8 ;
        if( all != bin.length ) {
            throw new IOException( "[long]プロトコル内容は不正です("+
                all+"/"+bin.length+")" ) ;
        }
        return ConvertParam.convertLong( ProtocolDef.SEND_OFFSET,bin ) ;
    }
    
    private static final byte[] dataResult( byte[] bin ) throws Exception {
        int len = ConvertParam.convertInt( ProtocolDef.SEND_OFFSET,bin ) ;
        int all = ProtocolDef.SEND_OFFSET + 4 + len ;
        if( all != bin.length ) {
            throw new IOException( "[data]プロトコル内容は不正です("+
                all+"/"+bin.length+")" ) ;
        }
        if( len <= 0 ) {
            return null ;
        }
        byte[] ret = new byte[ len ] ;
        System.arraycopy( bin,ProtocolDef.SEND_OFFSET+4,ret,0,len ) ;
        return ret ;
    }
    
    private static final ArrayList<byte[]> dataResultKey( byte[] bin ) throws Exception {
        int len = ConvertParam.convertInt( ProtocolDef.SEND_OFFSET,bin ) ;
        int pos = ProtocolDef.SEND_OFFSET + 4 ;
        ArrayList<byte[]> ret = new ArrayList<byte[]>() ;
        for( int i = 0 ; i < len ; i ++ ) {
            int bLen = ConvertParam.convertInt( pos,bin ) ;
            pos += 4 ;
            byte[] b = new byte[ bLen ] ;
            System.arraycopy( bin,pos,b,0,bLen ) ;
            pos += bLen ;
            ret.add( b ) ;
        }
        return ret ;
    }
    
    /**
     * 書き込みデータを取得.
     */
    private static final OutputStream getOutputStream() throws Exception {
        return new ByteArrayOutputStream() ;
    }
    
    /**
     * 書き込みデータを送信.
     */
    private static final byte[] sendRecv( ClientConnector conns,OutputStream data )
        throws Exception {
        byte[] b = ( ( ByteArrayOutputStream )data ).toByteArray() ;
        return conns.sendReceive( b ) ;
    }
    
}
