package org.maachang.dao.dbms.replication ;

import java.io.IOException;

import org.maachang.dao.dbms.DbUtil;
import org.maachang.util.BinaryBuffer;
import org.maachang.util.BinaryUtil;
import org.maachang.util.ConvertParam;

import com.y_ys.cluster.ha.ReplicationValue;
import com.y_ys.cluster.ha.ReplicationUtil;

/**
 * SQLレプリケーション情報.
 * 
 * @version 2008/11/08
 * @author masahito suzuki
 * @since MaachangDao 1.11
 */
public class ReplicationSQL implements ReplicationValue {
    
    /**
     * レプリケーションタイプ : DB.
     */
    public static final int TYPE = 0 ;
    
    /**
     * レプリケーションコマンドタイプ : Insert.
     */
    public static final int COMMAND_INSERT = 1 ;
    
    /**
     * レプリケーションコマンドタイプ : Update.
     */
    public static final int COMMAND_UPDATE = 2 ;
    
    /**
     * レプリケーションコマンドタイプ : Delete.
     */
    public static final int COMMAND_DELETE = 3 ;
    
    /**
     * コマンドタイプ.
     */
    private int command = -1 ;
    
    /**
     * テーブル名.
     */
    private String tableName = null ;
    
    /**
     * SQL文.
     */
    private String sql = null ;
    
    /**
     * パラメータ群.
     */
    private Object[] params = null ;
    
    /**
     * コンストラクタ.
     */
    private ReplicationSQL() {}
    
    /**
     * コンストラクタ.
     * @param command 対象のコマンドタイプを設定します.
     * @param tableName 対象のテーブル名を設定します.
     * @param sql 対象のSQL文を設定します.
     * @param params 対象のパラメータ名を設定します.
     * @exception Exception 例外.
     */
    public ReplicationSQL( int command,String tableName,String sql,Object[] params )
        throws Exception {
        if( ( tableName = DbUtil.convertJavaNameByDBName( tableName ) ) == null || 
            sql == null || ( sql = sql.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        switch( command ) {
            case COMMAND_INSERT:
            case COMMAND_UPDATE:
            case COMMAND_DELETE:
                break ;
            default :
                throw new IllegalArgumentException( "コマンド定義(" + command + ")が不正です" ) ;
        }
        if( params == null || params.length <= 0 ) {
            params = null ;
        }
        this.command = command ;
        this.tableName = tableName ;
        this.sql = sql ;
        this.params = params ;
    }
    
    protected void finalize() throws Exception {
        this.command = -1 ;
        this.tableName = null ;
        this.sql = null ;
        this.params = null ;
    }
    
    /**
     * オブジェクト破棄.
     */
    public void destroy() {
        this.command = -1 ;
        this.tableName = null ;
        this.sql = null ;
        this.params = null ;
    }
    
    /**
     * レプリケーションタイプを取得.
     * @return int レプリケーションタイプが返されます.
     */
    public int type() {
        return TYPE ;
    }
    
    /**
     * レプリケーションタイプを文字列で取得.
     * @return String レプリケーションタイプが文字列で返されます.
     */
    public String typString() {
        return "DB" ;
    }
    
    /**
     * コマンドタイプを取得.
     * @return int コマンドタイプが返されます.
     */
    public int getCommand() {
        return command ;
    }
    
    /**
     * テーブル名を取得.
     * @return String テーブル名が返されます.
     */
    public String getTableName() {
        return tableName ;
    }
    
    /**
     * 実行SQLを取得.
     * @return String SQL文が返されます.
     */
    public String getSql() {
        return sql ;
    }
    
    /**
     * パラメータを取得.
     * @return Object[] パラメータが返されます.
     */
    public Object[] getParams() {
        return params ;
    }
    
    /**
     * 文字列で出力.
     * @return String 文字列で返されます.
     */
    public String toString() {
        StringBuilder buf = new StringBuilder() ;
        buf.append( " command:" ) ;
        switch( command ) {
            case COMMAND_INSERT: buf.append( "insert" ) ; break ;
            case COMMAND_UPDATE: buf.append( "update" ) ; break ;
            case COMMAND_DELETE: buf.append( "delete" ) ; break ;
            default : buf.append( "unknown" ) ; break ;
        }
        buf.append( " table:" ).append( tableName ) ;
        buf.append( " sql:[" ).append(  sql ).append( "]" ) ;
        buf.append( " params(" ) ;
        if( params != null ) {
            int len = params.length ;
            buf.append( len ).append( "): {" ) ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( i != 0 ) {
                    buf.append( "," ) ;
                }
                if( params[ i ] == null ) {
                    buf.append( " null" ) ;
                }
                else if( params[ i ] instanceof byte[] ) {
                    buf.append( " " ).append( ( ( byte[] )params[ i ] ).length ).append( "@" ).
                    append( "byte[]" ) ;
                }
                else {
                    buf.append( " " ).append( params[ i ] ).append( "@" ).
                        append( params[ i ].getClass().getName() ) ;
                }
            }
            buf.append( " }" ) ;
        }
        else {
            buf.append( "0)" ) ;
        }
        return buf.toString() ;
    }
    
    // パラメータ型定義.
    private static final int PARAMS_NULL = 0 ;
    private static final int PARAMS_BOOL = 1 ;
    private static final int PARAMS_INTEGER = 2 ;
    private static final int PARAMS_LONG = 3 ;
    private static final int PARAMS_DOUBLE = 4 ;
    private static final int PARAMS_FLOAT = 5 ;
    private static final int PARAMS_DATE = 10 ;
    private static final int PARAMS_TIME = 11 ;
    private static final int PARAMS_TIMESTAMP = 12 ;
    private static final int PARAMS_STRING = 20 ;
    private static final int PARAMS_BINARY = 30 ;
    
    /**
     * Import.
     * @param binary 対象のバイナリを設定します.
     * @return ReplicationSQL レプリケーションオブジェクトが返されます.
     * @exception Exception 例外.
     */
    public static ReplicationSQL importReplication( byte[] binary )
        throws Exception {
        ReplicationUtil.checkReplicationProtocol( binary,TYPE ) ;
        int len ;
        int p = MIN_LENGTH ;
        //** データ領域開始 **//
        // コマンドタイプを取得.
        int cmd = ConvertParam.convertInt( p,binary ) ;
        p += 4 ;
        // テーブル名を取得.
        len = ConvertParam.convertInt( p,binary ) ;
        p += 4 ;
        String tblName = new String( binary,p,len,"UTF8" ) ;
        p += len ;
        // SQL文を取得.
        len = ConvertParam.convertInt( p,binary ) ;
        p += 4 ;
        String sql = new String( binary,p,len,"UTF8" ) ;
        p += len ;
        // パラメータ長を取得.
        int paramLen = ConvertParam.convertInt( p,binary ) ;
        p += 4 ;
        // パラメータ群を取得.
        Object[] pms = null ;
        if( paramLen > 0 ) {
            pms = new Object[ paramLen ] ;
            for( int i = 0 ; i < paramLen ; i ++ ) {
                // パラメータタイプを取得.
                int paramType = ( int )( binary[ p ] & 0x000000ff ) ;
                p += 1 ;
                switch( paramType ) {
                    case PARAMS_NULL :
                        break ;
                    case PARAMS_BOOL :
                        pms[ i ] = ( binary[ p ] == 0 ) ? Boolean.FALSE : Boolean.TRUE ;
                        p += 1 ;
                        break ;
                    case PARAMS_INTEGER :
                        pms[ i ] = new Integer( ConvertParam.convertInt( p,binary ) ) ;
                        p += 4 ;
                        break ;
                    case PARAMS_LONG :
                        pms[ i ] = new Long( ConvertParam.convertLong( p,binary ) ) ;
                        p += 8 ;
                        break ;
                    case PARAMS_DOUBLE :
                        pms[ i ] = ConvertParam.convertDoubleObject( p,binary ) ;
                        p += ( int )ConvertParam.convertShort( p,binary ) + 2 ;
                        break ;
                    case PARAMS_FLOAT :
                        pms[ i ] = ConvertParam.convertFloatObject( p,binary ) ;
                        p += ( int )ConvertParam.convertShort( p,binary ) + 2 ;
                        break ;
                    case PARAMS_DATE :
                        pms[ i ] = new java.sql.Date( ConvertParam.convertLong( p,binary ) ) ;
                        p += 8 ;
                        break ;
                    case PARAMS_TIME :
                        pms[ i ] = new java.sql.Time( ConvertParam.convertLong( p,binary ) ) ;
                        p += 8 ;
                        break ;
                    case PARAMS_TIMESTAMP :
                        pms[ i ] = new java.sql.Timestamp( ConvertParam.convertLong( p,binary ) ) ;
                        p += 8 ;
                        break ;
                    case PARAMS_STRING :
                        len = ConvertParam.convertInt( p,binary ) ;
                        p += 4 ;
                        if( len <= 0 ) {
                            pms[ i ] = "" ;
                        }
                        else {
                            pms[ i ] = new String( binary,p,len,"UTF8" ) ;
                            p += len ;
                        }
                        break ;
                    case PARAMS_BINARY :
                        len = ConvertParam.convertInt( p,binary ) ;
                        p += 4 ;
                        if( len <= 0 ) {
                            pms[ i ] = new byte[ 0 ] ;
                        }
                        else {
                            byte[] b = new byte[ len ] ;
                            System.arraycopy( binary,p,b,0,len ) ;
                            pms[ i ] = b ;
                            p += len ;
                        }
                        break ;
                    default :
                        throw new IOException( "[" + i + "]:パラメータタイプ(" + paramType + ")は不正です" ) ;
                }
            }
        }
        return new ReplicationSQL( cmd,tblName,sql,pms ) ;
    }
    
    /**
     * Export.
     * @return byte[] 変換されたバイナリが返されます.
     * @exception Exception 例外.
     */
    public byte[] exportReplication() throws Exception {
        return exportReplication( command,tableName,sql,params ) ;
    }
    
    /**
     * Export.
     * @param command 対象のコマンドタイプを設定します.
     * @param tableName 対象のテーブル名を設定します.
     * @param sql 対象のSQL文を設定します.
     * @param params 対象のパラメータ名を設定します.
     * @return byte[] 変換されたバイナリが返されます.
     * @exception Exception 例外.
     */
    public static final byte[] exportReplication( int command,String tableName,String sql,Object[] params )
        throws Exception {
        if( ( tableName = DbUtil.convertJavaNameByDBName( tableName ) ) == null || 
            sql == null || ( sql = sql.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        switch( command ) {
            case COMMAND_INSERT:
            case COMMAND_UPDATE:
            case COMMAND_DELETE:
                break ;
            default :
                throw new IllegalArgumentException( "コマンド定義(" + command + ")が不正です" ) ;
        }
        if( params == null || params.length <= 0 ) {
            params = null ;
        }
        byte[] b = null ;
        BinaryBuffer bin = new BinaryBuffer() ;
        bin.position( REPLICATION_OFFSET ) ;
        // レプリケーションタイプ.
        bin.write( ConvertParam.convertInt( TYPE ) ) ;
        //** データ領域開始 **//
        // コマンドタイプ.
        bin.write( ConvertParam.convertInt( command ) ) ;
        // テーブル名.
        b = tableName.getBytes( "UTF8" ) ;
        bin.write( ConvertParam.convertInt( b.length ) ) ;
        bin.write( b ) ;
        // SQL.
        b = sql.getBytes( "UTF8" ) ;
        bin.write( ConvertParam.convertInt( b.length ) ) ;
        bin.write( b ) ;
        b = null ;
        // パラメータ群.
        int paramLen = ( params == null ) ? 0 : params.length ;
        bin.write( ConvertParam.convertInt( paramLen ) ) ;
        for( int i = 0 ; i < paramLen ; i ++ ) {
            Object o = params[ i ] ;
            if( o == null ) {
                bin.write( PARAMS_NULL ) ;
            }
            else if( o instanceof Boolean ) {
                bin.write( PARAMS_BOOL ) ;
                bin.write( ( ( ( Boolean )o ).booleanValue() ) ? 1 : 0 ) ;
            }
            else if( o instanceof Integer ) {
                bin.write( PARAMS_INTEGER ) ;
                bin.write( ConvertParam.convertInt( ( ( Integer )o ).intValue() ) ) ;
            }
            else if( o instanceof Long ) {
                bin.write( PARAMS_LONG ) ;
                bin.write( ConvertParam.convertLong( ( ( Long )o ).longValue() ) ) ;
            }
            else if( o instanceof Double ) {
                bin.write( PARAMS_DOUBLE ) ;
                bin.write( ConvertParam.convertDouble( ( ( Double )o ).doubleValue() ) ) ;
            }
            else if( o instanceof Float ) {
                bin.write( PARAMS_FLOAT ) ;
                bin.write( ConvertParam.convertFloat( ( ( Float )o ).floatValue() ) ) ;
            }
            else if( o instanceof java.sql.Date ) {
                bin.write( PARAMS_DATE ) ;
                bin.write( ConvertParam.convertLong( ( ( java.sql.Date )o ).getTime() ) ) ;
            }
            else if( o instanceof java.sql.Time ) {
                bin.write( PARAMS_TIME ) ;
                bin.write( ConvertParam.convertLong( ( ( java.sql.Time )o ).getTime() ) ) ;
            }
            else if( o instanceof java.sql.Timestamp ) {
                bin.write( PARAMS_TIMESTAMP ) ;
                bin.write( ConvertParam.convertLong( ( ( java.sql.Timestamp )o ).getTime() ) ) ;
            }
            else if( o instanceof String ) {
                bin.write( PARAMS_STRING ) ;
                if( ( ( String )o ).length() <= 0 ) {
                    bin.write( ConvertParam.convertInt( 0 ) ) ;
                }
                else {
                    b = ( ( String )o ).getBytes( "UTF8" ) ;
                    bin.write( ConvertParam.convertInt( b.length ) ) ;
                    bin.write( b ) ;
                    b = null ;
                }
            }
            else if( o instanceof byte[] ) {
                bin.write( PARAMS_BINARY ) ;
                if( ( ( byte[] )o ).length <= 0 ) {
                    bin.write( ConvertParam.convertInt( 0 ) ) ;
                }
                else {
                    bin.write( ConvertParam.convertInt( ( ( byte[] )o ).length ) ) ;
                    bin.write( ( byte[] )o ) ;
                }
            }
            else {
                throw new IOException( "不正なオブジェクト型(" + o.getClass().getName() + ")です" ) ;
            }
        }
        // 全体長を取得.
        int allLen = bin.position() ;
        // マジックNo設定.
        bin.position( MAGIC_NO_OFFSET ) ;
        bin.write( MAGIC_NO ) ;
        // 全体長を設定.
        bin.write( ConvertParam.convertInt( allLen ) ) ;
        // プロトコル生成時間を設定.
        bin.position( CREATE_TIME_OFFSET ) ;
        bin.write( ConvertParam.convertLong( System.currentTimeMillis() ) ) ;
        // 生成完了.
        return bin.getBinary( allLen ) ;
    }
    
    /** test. **/
    public static final void main( String[] args ) throws Exception {
        String tableName = "TestTable" ;
        String sql = "insert into test_table ( id,name,name_kana,yen,img ) values ( ?,?,?,?,? )" ;
        Object[] params = { new Long(1),"穂下太郎","ホゲタロウ",new Double( 100.234f ),new byte[]{0,1,2,3,4,5} } ;
        ReplicationSQL rep = new ReplicationSQL( COMMAND_INSERT,tableName,sql,params ) ;
        System.out.println( "create:" + rep ) ;        
        byte[] bin = rep.exportReplication() ;
        System.out.println( " export:" + bin.length ) ;
        System.out.println( BinaryUtil.toString( bin ) ) ;
        rep = ReplicationSQL.importReplication( bin ) ;
        System.out.println( "import:" + rep ) ;
    }
}
