package org.maachang.mimdb.core.impl ;

import java.io.OutputStream;
import java.util.List;

import org.maachang.mimdb.MimdbException;
import org.maachang.mimdb.core.BaseTable;
import org.maachang.mimdb.core.MimdbSearchElement;
import org.maachang.mimdb.core.MimdbSearchType;
import org.maachang.mimdb.core.util.ObjectBinary;

/**
 * メモリ検索要素情報実装.
 * 
 * @version 2013/10/07
 * @author masahito suzuki
 * @since MasterInMemDB 1.00
 */
public final class MSearchElement implements MimdbSearchElement {
    protected int type ;
    protected String column ;
    protected Object value ;
    protected int columnNo ;
    protected boolean preparedType ;
    
    /**
     * コンストラクタ.
     */
    public MSearchElement() {}
    
    /**
     * 検索条件を設定.
     * この条件は、括弧やand,orの条件のみが設定可能です.
     * @param type 検索条件を設定します.
     * @exception Exception 例外.
     */
    public MSearchElement( final int type )
        throws Exception {
        this.create( type ) ;
    }
    
    /**
     * 検索条件を設定.
     * この条件は、検索条件に対する括弧、and,orの条件のみが設定可能です.
     * @param type 検索条件を設定します.
     * @exception Exception 例外.
     */
    public MSearchElement( final String type )
        throws Exception {
        this.create( type ) ;
    }
    
    /**
     * 検索条件を設定.
     * この条件は、preparedモードで設定されます.
     * この条件は、比較カラムに対する条件指定の検索対象となります.
     * @param column 検索元のカラム名を設定します.
     * @param type 検索条件を文字で設定します.
     * @exception Exception 例外.
     */
    public MSearchElement( final String column,final String type )
        throws Exception {
        this.create( column,type ) ;
    }
    
    /**
     * コンストラクタ.
     * この条件は、preparedモードで設定されます.
     * この条件は、比較カラムに対する条件指定の検索対象となります.
     * @param column 検索元のカラム名を設定します.
     * @param type 検索条件を設定します.
     * @exception Exception 例外.
     */
    public MSearchElement( final String column,final int type )
        throws Exception {
        this.create( column,type ) ;
    }
    
    /**
     * 検索条件を設定.
     * この条件は、比較カラムに対する条件指定の検索対象となります.
     * @param column 検索元のカラム名を設定します.
     * @param type 検索条件を文字で設定します.
     * @param value 検索要素を設定します.
     * @exception Exception 例外.
     */
    public MSearchElement( final String column,final String type,final Object value )
        throws Exception {
        this.create( column,type,value ) ;
    }
    
    /**
     * コンストラクタ.
     * この条件は、比較カラムに対する条件指定の検索対象となります.
     * @param column 検索元のカラム名を設定します.
     * @param type 検索条件を設定します.
     * @param value 検索要素を設定します.
     * @exception Exception 例外.
     */
    public MSearchElement( final String column,final int type,final Object value )
        throws Exception {
        this.create( column,type,value ) ;
    }
    
    /**
     * 検索条件を設定.
     * この条件は、preparedモードで設定されます.
     * この条件は、比較カラムに対する条件指定の検索対象となります.
     * @param column 検索元のカラム名を設定します.
     * @param type 検索条件を文字で設定します.
     * @exception Exception 例外.
     */
    public void create( final String column,final String type )
        throws Exception {
        create( column,type,null,true ) ;
    }
    
    /**
     * 検索条件を設定.
     * この条件は、preparedモードで設定されます.
     * この条件は、比較カラムに対する条件指定の検索対象となります.
     * @param column 検索元のカラム名を設定します.
     * @param type 検索条件を設定します.
     * @exception Exception 例外.
     */
    public void create( final String column,final int type )
        throws Exception {
        create( column,type,null,true ) ;
    }
    
    /**
     * 検索条件を設定.
     * この条件は、比較カラムに対する条件指定の検索対象となります.
     * @param column 検索元のカラム名を設定します.
     * @param type 検索条件を文字で設定します.
     * @param value 検索要素を設定します.
     * @exception Exception 例外.
     */
    public void create( final String column,final String type,final Object value )
        throws Exception {
        create( column,type,value,false ) ;
    }
    
    /**
     * 検索条件を設定.
     * この条件は、比較カラムに対する条件指定の検索対象となります.
     * @param column 検索元のカラム名を設定します.
     * @param type 検索条件を設定します.
     * @param value 検索要素を設定します.
     * @exception Exception 例外.
     */
    public void create( final String column,final int type,final Object value )
        throws Exception {
        create( column,type,value,false ) ;
    }
    
    /**
     * 検索条件を設定.
     * この条件は、検索条件に対する括弧、and,orの条件のみが設定可能です.
     * @param type 検索条件を設定します.
     * @exception Exception 例外.
     */
    public void create( String type )
        throws Exception {
        if( type == null || ( type = type.trim().toLowerCase() ).length() <= 0 ) {
            throw new MimdbException( "検索条件が存在しません" ) ;
        }
        int t = -1 ;
        if( "(".equals( type ) ) {
            t = MimdbSearchType.PAR_START ;
        }
        else if( ")".equals( type ) ) {
            t = MimdbSearchType.PAR_END ;
        }
        else if( "and".equals( type ) ) {
            t = MimdbSearchType.AND ;
        }
        else if( "or".equals( type ) ) {
            t = MimdbSearchType.OR ;
        }
        else {
            throw new MimdbException( "利用できない検索条件[" + type + "]が設定されています" ) ;
        }
        create( t ) ;
    }
    
    /**
     * 検索条件を設定.
     * @param type 検索条件を設定します.
     * @exception Exception 例外.
     */
    public void create( final int type )
        throws Exception {
        switch( type ) {
            case MimdbSearchType.PAR_START :
            case MimdbSearchType.PAR_END :
            case MimdbSearchType.AND :
            case MimdbSearchType.OR :
                break ;
            default :
                throw new MimdbException( "不明な検索条件が設定されています[" + type + "]" ) ;
        }
        this.column = null ;
        this.type = type ;
        this.value = null ;
        this.columnNo = -1 ;
        this.preparedType = false ;
    }
    
    /**
     * 検索条件を設定.
     * この条件は、比較カラムに対する条件指定の検索対象となります.
     * @param column 検索元のカラム名を設定します.
     * @param type 検索条件を文字で設定します.
     * @param value 検索要素を設定します.
     * @param preparentType [true]の場合、preparedモードとなります.
     * @exception Exception 例外.
     */
    protected void create( final String column,String type,final Object value,final boolean preparedType )
        throws Exception {
        if( type == null || ( type = type.trim().toLowerCase() ).length() <= 0 ) {
            throw new MimdbException( "検索条件が存在しません" ) ;
        }
        int t = -1 ;
        if( "=".equals( type ) ) {
            t = MimdbSearchType.TYPE_EQ ;
        }
        else if( "!=".equals( type ) || "<>".equals( type ) ) {
            t = MimdbSearchType.TYPE_NEQ ;
        }
        else if( ">".equals( type ) ) {
            t = MimdbSearchType.TYPE_LBIG ;
        }
        else if( ">=".equals( type ) ) {
            t = MimdbSearchType.TYPE_LEQBIG ;
        }
        else if( "<".equals( type ) ) {
            t = MimdbSearchType.TYPE_RBIG ;
        }
        else if( "<=".equals( type ) ) {
            t = MimdbSearchType.TYPE_REQBIG ;
        }
        else if( "in".equals( type ) ) {
            t = MimdbSearchType.TYPE_IN ;
        }
        else if( "like".equals( type ) ) {
            t = MimdbSearchType.TYPE_LIKE ;
        }
        else if( "between".equals( type ) ) {
            t = MimdbSearchType.TYPE_BETWEEN ;
        }
        else {
            // カラム名がオフセットおよび、リミットの場合.
            if( "offset".equals( column ) || "limit".equals( column ) ) {
                t = MimdbSearchType.OFF_LIMIT ;
            }
            else {
                throw new MimdbException( "利用できない検索条件[" + type + "]が設定されています" ) ;
            }
        }
        create( column,t,value,preparedType ) ;
    }
    
    /**
     * 検索条件を設定.
     * この条件は、比較カラムに対する条件指定の検索対象となります.
     * @param column 検索元のカラム名を設定します.
     * @param type 検索条件を設定します.
     * @param value 検索要素を設定します.
     * @param preparentType [true]の場合、preparedモードとなります.
     * @exception Exception 例外.
     */
    protected void create( String column,final int type,final Object value,final boolean preparedType )
        throws Exception {
        if( column == null || ( column = column.trim().toLowerCase() ).length() <= 0 ) {
            throw new MimdbException( "比較対象のカラム名が設定されていません" ) ;
        }
        if( value == null ) {
            switch( type ) {
                case MimdbSearchType.TYPE_EQ :
                case MimdbSearchType.TYPE_NEQ :
                    break ;
                default :
                    throw new MimdbException( "NULL検索で利用できない条件が設定されています[" + type + "]" ) ;
            }
        }
        else {
            switch( type ) {
                case MimdbSearchType.TYPE_EQ :
                case MimdbSearchType.TYPE_NEQ :
                case MimdbSearchType.TYPE_LBIG :
                case MimdbSearchType.TYPE_LEQBIG :
                case MimdbSearchType.TYPE_RBIG :
                case MimdbSearchType.TYPE_REQBIG :
                case MimdbSearchType.TYPE_IN :
                case MimdbSearchType.TYPE_LIKE :
                case MimdbSearchType.TYPE_BETWEEN :
                case MimdbSearchType.OFF_LIMIT :
                    break ;
                default :
                    throw new MimdbException( "不明な検索条件が設定されています[" + type + "]" ) ;
            }
        }
        this.column = column ;
        this.type = type ;
        this.value = value ;
        this.columnNo = -1 ;
        this.preparedType = preparedType ;
    }
    
    /**
     * 情報クリア.
     */
    public void clear() {
        type = -1 ;
        columnNo = -1 ;
        column = null ;
        value = null ;
        preparedType = false ;
    }
    
    /**
     * 対象カラム名を取得.
     * @return String 比較対象のカラム名を取得します.
     */
    public String getColumn() {
        return column ;
    }
    
    /**
     * 検索タイプの取得.
     * @return int 検索タイプが返却されます.
     */
    public int getType() {
        return this.type ;
    }
    
    /**
     * 検索要素の取得.
     * @return Object 検索要素が返却されます.
     */
    public Object getValue() {
        return this.value ;
    }
    
    /**
     * 検索要素の設定.
     * @param v 検索要素を設定します.
     */
    public void setValue( final Object v ) {
        this.value = v ;
    }
    
    /**
     * 検索条件がNULLかチェック.
     * @return boolean [true]の場合はNULLです.
     */
    public boolean isNull() {
        return this.value == null ;
    }
    
    /**
     * Preparedモードかチェック.
     * @return boolean [true]の場合、Preparedモードです.
     */
    public boolean isPrepared() {
        return preparedType ;
    }
    
    /**
     * 条件指定かチェック.
     * @return boolean [true]の場合、a=bのような条件指定です.
     */
    public boolean isWhere() {
        switch( type ) {
            case MimdbSearchType.PAR_START :
            case MimdbSearchType.PAR_END :
            case MimdbSearchType.AND :
            case MimdbSearchType.OR :
                return false ;
        }
        return true ;
    }
    
    /**
     * 条件指定かチェック.
     * @return boolean [true]の場合、and orのような条件指定です.
     */
    public boolean isAndOr() {
        switch( type ) {
            case MimdbSearchType.AND :
            case MimdbSearchType.OR :
                return true ;
        }
        return true ;
    }
    
    /**
     * オフセット、リミット条件かチェック.
     * @return boolean [true]の場合、オフセット、リミット条件です.
     */
    public boolean isOffLimit() {
        return type == MimdbSearchType.OFF_LIMIT ;
    }
    
    /**
     * カラム名を番号に変換.
     * @param table 対象のテーブルを設定します.
     * @return int カラム項番が返却されます.
     * @exception Exception 例外.
     */
    public int columnNameByNo( final BaseTable table )
        throws Exception {
        // カラム名が存在する条件だけ対象とする.
        if( column != null ) {
            columnNo = table.getColumnNameByNo( column ) ;
            if( columnNo == -1 ) {
                throw new MimdbException( "テーブル[" + table.getName() + "]に、カラム名[" + column + "]は存在しません" ) ;
            }
            return columnNo ;
        }
        throw new MimdbException( "カラム名は存在しません" ) ;
    }
    
    /**
     * カラム項番を取得.
     * @return int カラム項番が返却されます.
     */
    public int getColumnNo() {
        return columnNo ;
    }
    
    /**
     * 文字列変換.
     * @return String 文字列に変換されます.
     */
    public String toString() {
        return toStringStatic( type,column,value,preparedType ) ;
    }
    
    /** 文字変換. **/
    protected static final String toStringStatic( int type,String column,Object value,boolean preparedType ) {
        switch( type ) {
            case MimdbSearchType.PAR_START :
                return "(" ;
            case MimdbSearchType.PAR_END :
                return ")" ;
            case MimdbSearchType.AND :
                return "and" ;
            case MimdbSearchType.OR :
                return "or" ;
        }
        
        StringBuilder buf = new StringBuilder() ;
        switch( type ) {
            case MimdbSearchType.TYPE_EQ :
                return buf.append( column ).append( " = " ).append( valueString( value,preparedType ) ).toString() ;
            case MimdbSearchType.TYPE_NEQ :
                return buf.append( column ).append( " != " ).append( valueString( value,preparedType ) ).toString() ;
            case MimdbSearchType.TYPE_LBIG :
                return buf.append( column ).append( " > " ).append( valueString( value,preparedType ) ).toString() ;
            case MimdbSearchType.TYPE_LEQBIG :
                return buf.append( column ).append( " >= " ).append( valueString( value,preparedType ) ).toString() ;
            case MimdbSearchType.TYPE_RBIG :
                return buf.append( column ).append( " <= " ).append( valueString( value,preparedType ) ).toString() ;
            case MimdbSearchType.TYPE_REQBIG :
                return buf.append( column ).append( " <= " ).append( valueString( value,preparedType ) ).toString() ;
            case MimdbSearchType.TYPE_IN :
                if( !preparedType && value instanceof List ) {
                    buf.append( column ).append( " IN ( " ) ;
                    List n = (List)value ;
                    int len = n.size() ;
                    for( int i = 0 ; i < len ; i ++ ) {
                        if( i != 0 ) {
                            buf.append( " , " ) ;
                        }
                        buf.append( valueString( n.get( i ),preparedType ) ) ;
                    }
                    return buf.append( " )" ).toString() ;
                }
                else {
                    return buf.append( column ).append( " IN ( " ).append( valueString( value,preparedType ) ).append( " )" ).toString() ;
                }
            case MimdbSearchType.TYPE_LIKE :
                return buf.append( column ).append( " LIKE " ).append( valueString( value,preparedType ) ).toString() ;
            
            case MimdbSearchType.TYPE_BETWEEN :
                if( !preparedType && value instanceof List ) {
                    buf.append( column ).append( " BETWEEN ( " ) ;
                    List n = (List)value ;
                    int len = n.size() ;
                    for( int i = 0 ; i < len ; i ++ ) {
                        if( i != 0 ) {
                            buf.append( " , " ) ;
                        }
                        buf.append( valueString( n.get( i ),preparedType ) ) ;
                    }
                    return buf.append( " )" ).toString() ;
                }
                else {
                    return buf.append( column ).append( " BETWEEN ( " ).append( valueString( value,preparedType ) ).append( " )" ).toString() ;
                }
            
            case MimdbSearchType.OFF_LIMIT :
                return buf.append( column ).append( " " ).append( valueString( value,preparedType ) ).toString() ;
        }
        
        return "" ;
    }
    
    /** 文字列の場合は、コーテーションをつける. **/
    private static final String valueString( Object o,boolean preparedType ) {
        if( preparedType ) {
            return "?" ;
        }
        if( o instanceof String ) {
            return new StringBuilder().append( "'" ).append( o ).append( "'" ).toString() ;
        }
        else if( o == null ) {
            return "NULL" ;
        }
        return o.toString() ;
    }
    
    /**
     * バイナリ変換.
     * @param out 対象のOutputStreamを設定します.
     * @exception Exception 例外.
     */
    public void getOutput( final OutputStream out )
        throws Exception {
        ObjectBinary.encode( out,type ) ;
        ObjectBinary.encode( out,column ) ;
        ObjectBinary.encode( out,value ) ;
        ObjectBinary.encode( out,columnNo ) ;
        ObjectBinary.encode( out,preparedType ) ;
    }
}
