package org.maachang.mimdb.core.impl;

import org.maachang.mimdb.MimdbException;
import org.maachang.mimdb.core.util.ObjectList;

/**
 * Like文の解析.
 * 
 * @version 2013/10/13
 * @author masahito suzuki
 * @since MasterInMemDB 1.00
 */
public final class LikeAnalyzer {
    
    private LikeAnalyzer() {}
    
    /**
     * Like文の解析.
     * Like検索は、[%,_]をサポート.
     * [%][*]は不明な複数文字が設定されている内容.
     * [_][?]は不明な１文字が設定されている内容.
     * @param like Like構文を設定します.
     * @return LikeParser Like構文がパースされた内容が返却されます.
     * @exception Exception 例外.
     */
    public static final LikeParser parser( final String like ) throws Exception {
        if( like == null || like.length() <= 0 ) {
            throw new MimdbException( "Likeパラメータが存在しません" ) ;
        }
        StringBuilder buf = null ;
        char c ;
        boolean yenFlag = false ;
        int type = -1 ;
        int pos = 0 ;
        final int len = like.length() ;
        final ObjectList<Object> ret = new ObjectList<Object>() ;
        for( int i = 0 ; i < len ; i ++ ) {
            c = like.charAt( i ) ;
            // ターゲットポイント.
            if( !yenFlag && ( c == '%' || c == '*' || c == '_' || c == '?' ) ) {
                // データが存在する場合.
                if( buf != null ) {
                    if( ret.size() == 0 ) {
                        ret.add( LikeParser.POS_FIRST ) ;
                        // 初期化.
                        type = -1 ;
                        pos = 0 ;
                    }
                    ret.add( buf.toString() ) ;
                    buf = null ;
                }
                if( c == '%' || c == '*' ) {
                    if( type != 0 ) {
                        ret.add( LikeParser.POS_BETWEEN ) ;
                    }
                    type = 0 ; // 文字数無限.
                    pos = 0 ;
                }
                else if( c == '_' || c == '?' ) {
                    if( type == -1 ) {
                        type = 1 ; // 文字数指定.
                        pos = 1 ;
                    }
                    // 連続文字数指定.
                    else if( type == 1 ) {
                        pos ++ ;
                    }
                }
            }
            // 検索文字列.
            else {
                if( c == '\\' ) {
                    yenFlag = true ;
                    continue ;
                }
                if( buf == null ) {
                    if( type == 1 ) { // 文字数指定
                        ret.add( pos ) ;
                    }
                    buf = new StringBuilder() ;
                }
                buf.append( c ) ;
                // 初期化.
                type = -1 ;
                pos = 0 ;
            }
            // \\ではない.
            yenFlag = false ;
        }
        // 後処理.
        if( type == 0 ) { // 文字数無限.
            if( buf != null ) {
                ret.add( buf.toString() ) ;
                buf = null ;
            }
            if( (Integer )ret.get( ret.size() -1 ) != LikeParser.POS_BETWEEN ) {
                ret.add( LikeParser.POS_BETWEEN ) ;
            }
        }
        else if( type == 1 && pos > 0 ) { // 文字数指定.
            if( buf != null ) {
                ret.add( buf.toString() ) ;
                buf = null ;
            }
            ret.add( pos ) ;
        }
        else if( buf != null ) { // 検索文字が残っている.
            if( ret.size() == 0 ) {
                ret.add( LikeParser.POS_FIRST ) ;
            }
            ret.add( LikeParser.POS_LAST ) ;
            ret.add( buf.toString() ) ;
            buf = null ;
        }
        return new LikeParserImpl( ret.getArray(),like ) ;
    }
    
    /**
     * マッチしているかチェック.
     * @param parser パーサーを設定します.
     * @param value 文字列を設定します.
     * @return boolean [true]の場合、マッチしています.
     * @exception Exception 例外.
     */
    public static final boolean match( final LikeParser parser,final String value )
        throws Exception {
        if( value == null || value.length() <= 0 ) {
            return false ;
        }
        final Object[] ps = ( ( LikeParserImpl )parser ).parser ;
        final int len = ps.length ;
        // [特殊条件]内容が１文字の場合は、％指定のみか、_文字数定義.
        if( len == 1 ) {
            final int n = (Integer)ps[ 0 ] ;
            if( n == LikeParser.POS_BETWEEN ) {
                return true ;
            }
            // 長さ一致.
            return value.length() == n ;
        }
        // [特殊条件]パース条件が3文字の場合.
        if( len == 3 ) {
            // Like構文ではなく、完全一致条件の場合.
            if( ps[ 0 ] instanceof Integer &&
                ps[ 1 ] instanceof Integer &&
                ps[ 2 ] instanceof String &&
                ( Integer )ps[ 0 ] == LikeParser.POS_FIRST &&
                ( Integer )ps[ 1 ] == LikeParser.POS_LAST ) {
                return value.equals( ps[ 2 ] ) ;
            }
        }
        // 通常パース条件.
        int p = 0 ;
        int b = 0 ;
        int n = -99 ; // 定義不明.
        boolean last = false ;
        String bw = null ;
        String v ;
        for( int i = 0 ; i < len ; i ++ ) {
            // 条件指定.
            if( ps[ i ] instanceof Integer ) {
                // 最後方一致条件の場合.
                if( (Integer)ps[ i ] == LikeParser.POS_LAST ) {
                    last = true ;
                }
                // 通常条件の場合.
                else {
                    n = (Integer)ps[ i ] ;
                }
            }
            // 文字指定.
            else {
                v = (String)ps[ i ] ;
                // 文字指定の場合.
                if( n > 0 ) {
                    if( bw == null ) {
                        if( value.length() < b + n + v.length() ||
                            !v.equals( value.substring( b + n,b + n + v.length() ) ) ) {
                            return false ;
                        }
                    }
                    else {
                        while( true ) {
                            if( value.length() < b + n + v.length() ||
                                !v.equals( value.substring( b + n,b + n + v.length() ) ) ) {
                                // 見つからない場合は、以前の部分検索条件が見つかるまで検索.
                                // 見つかった場合は、再度文字指定検索を再開.
                                if( ( p = value.indexOf( bw,b ) ) == -1 ) {
                                    return false ;
                                }
                                b = p + bw.length() ;
                            }
                            else {
                                break ;
                            }
                        }
                    }
                    // 最後方一致条件の場合は、現在の検索長と、文字列長が一致チェックで終了.
                    if( last ) {
                        return ( b + n + v.length() == value.length() ) ;
                    }
                    else {
                        b = b + n + v.length() ;
                    }
                    n = -99 ; // 定義不明.
                    bw = null ; // 前回のindexof条件をクリア.
                }
                // 条件指定の場合.
                // ただし最後方一致の場合は、そちらを優先させる.
                else if( !last ) {
                    switch( n ) {
                        case LikeParser.POS_FIRST : // 先頭一致.
                            if( !value.startsWith( v ) ) {
                                return false ;
                            }
                            b = v.length() ;
                            break ;
                        case LikeParser.POS_BETWEEN : // 次の文字列が一致.
                            if( ( p = value.indexOf( v,b ) ) == -1 ) {
                                return false ;
                            }
                            bw = v ; // 前回のindexof条件を保持.
                            b = p + v.length() ;
                            break ;
                        case -99 :
                            throw new MimdbException( "Like構文が不正です" ) ;
                    }
                }
                // 最後方一致条件の場合.
                if( last ) {
                    return value.endsWith( v ) ;
                }
            }
        }
        // 文字指定の場合.
        if( n > 0 ) {
            if( bw == null ) {
                if( value.length() != b + n ) {
                    return false ;
                }
            }
            else {
                while( true ) {
                    if( value.length() != b + n ) {
                        // 見つからない場合は、以前の部分検索条件が見つかるまで検索.
                        // 見つかった場合は、文字長一致検索を再開.
                        if( ( p = value.indexOf( bw,b ) ) == -1 ) {
                            return false ;
                        }
                        b = p + bw.length() ;
                    }
                    else {
                        break ;
                    }
                }
            }
        }
        return true ;
    }
    
}

/** Likeパーサー実装. **/
final class LikeParserImpl implements LikeParser {
    Object[] parser ;
    String src ;
    
    public LikeParserImpl( final Object[] p,final String s ) {
        parser = p ;
        src = s ;
    }
    
    /**
     * Like文の取得.
     * @return String Like文が返却されます.
     */
    public String getSrc() {
        return src ;
    }
    
    /**
     * パーサーを取得.
     * @param no 対象の項番を設定します.
     * @return Object 対象項番のパーサー情報が返却されます.
     */
    public Object get( final int no ) {
        return parser[ no ] ;
    }
    
    /**
     * 情報長の取得.
     * @return int 情報長が返却されます.
     */
    public int size() {
        return parser.length ;
    }
    
    /**
     * 全パーサー情報を取得.
     * @return Object[] 全パーサー情報が返却されます.
     */
    public Object[] getAll() {
        return parser ;
    }
    
    /**
     * パーサー内容を入れ替え.
     * @param o 入れ替え対象のオブジェクトを設定します.
     */
    public void set( final Object[] o ) {
        parser = o ;
    }
    
    /**
     * パーサー内容を文字列化.
     * @return String 文字列内容が返却されます.
     */
    public String toString() {
        StringBuilder buf = new StringBuilder() ;
        int len = parser.length ;
        Object n ;
        int nn ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( i != 0 ) {
                buf.append( " , " ) ;
            }
            n = parser[ i ] ;
            if( n instanceof Integer ) {
                nn = ( Integer )n ;
                if( nn == LikeParser.POS_FIRST ) {
                    buf.append( "[Start]" ) ;
                }
                else if( nn == LikeParser.POS_LAST ) {
                    buf.append( "[LAST]" ) ;
                }
                else if( nn == LikeParser.POS_BETWEEN ) {
                    buf.append( "[*]" ) ;
                }
                else {
                    buf.append( "[" ).append( nn ).append( ":LEN]" ) ;
                }
            }
            else {
                buf.append( n ) ;
            }
        }
        return buf.toString() ;
    }
    
}
