package org.maachang.comet.httpd.engine.script.taglib;

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

import org.maachang.comet.httpd.engine.script.js.JsDef;
import org.maachang.conf.Config;

/**
 * タグライブラリ解析処理.
 * 
 * @version 2008/07/18
 * @author masahito suzuki
 * @since MaachangComet 1.20
 */
public class AnalysisTagLib {
    
    /**
     * タグライブラリ開始タグ.
     */
    private static final String START_TAGLIB = "<$" ;
    
    /**
     * タグライブラリ終了タグ.
     */
    private static final String END_TAGLIB = ">" ;
    
    /**
     * パラメータ出力開始タグ.
     */
    private static final String START_PLIB = "${" ;
    
    /**
     * パラメータ出力終了タグ.
     */
    private static final String END_PLIB = "}" ;
    
    /**
     * パラメータ出力開始タグ(HTML変換版).
     */
    private static final String START_PHLIB = "@{" ;
    
    /**
     * パラメータ出力終了タグ(HTML変換版).
     */
    private static final String END_PHLIB = "}" ;
    
    /**
     * タグライブラリ解析.
     * @param string 解析内容を設定します.
     * @return String 解析結果が返されます.
     * @exception Exception 例外.
     */
    public static final String analysis( String string )
        throws Exception {
        string = tagLib( string ) ;
        string = varLib( string ) ;
        return varHtmlLib( string ) ;
    }
    
    /**
     * タグライブラリ解析処理.
     */
    private static final String tagLib( String string )
        throws Exception {
        int p = 0 ;
        int b = 0 ;
        if( ( p = string.indexOf( START_TAGLIB ) ) <= -1 ) {
            return string ;
        }
        StringBuilder buf = new StringBuilder() ;
        int[] outTagPos = new int[ 1 ] ;
        for( ;; ) {
            int e ;
            boolean parFlag ;
            String tagName = getTagLibName( outTagPos,string,p ) ;
            // タグ名予約語チェック.
            int parCd = parCode( tagName ) ;
            // タグ名が予約語の場合.
            if( parCd != -1 ) {
                e = indexToNotPar( string,END_TAGLIB,'(',')',p ) ;
                if( e <= -1 ) {
                    buf.append( string.substring( b ) ) ;
                    break ;
                }
                // 予約語で処理する.
                parFlag = true ;
            }
            // タグ名が予約語でない場合.
            else {
                e = indexToNotCote( string,END_TAGLIB,p ) ;
                if( e <= -1 ) {
                    buf.append( string.substring( b ) ) ;
                    break ;
                }
                // 定義タグライブラリで処理する.
                parFlag = false ;
            }
            // 前の終端位置から、次の開始位置までの内容をコピー.
            buf.append( string.substring( b,p ) ) ;
            
            // 予約語で処理する場合.
            if( parFlag ) {
                tagLibToPar( buf,string,tagName,parCd,outTagPos[ 0 ],e ) ;
            }
            // 定義タグライブラリで処理する場合.
            else {
                Config conf = DefineTagLib.getInstance().getConfig( tagName ) ;
                if( conf != null ) {
                    // ターゲットとなるタグ名で処理する.
                    tagLibToDef( buf,string,tagName,conf,outTagPos[ 0 ],e ) ;
                }
                else {
                    // 当てはまるタグが存在しない場合は、処理しない.
                    buf.append( string.substring( p,e+1 ) ) ;
                }
            }
            // 次の処理を行う準備.
            b = e+1 ;
            p = string.indexOf( START_TAGLIB,b ) ;
            if( p <= -1 ) {
                buf.append( string.substring( b ) ) ;
                break ;
            }
        }
        return buf.toString() ;
    }
    
    /**
     * 出力タグ解析処理.
     */
    private static final String varLib( String string )
        throws Exception {
        int p = 0 ;
        int b = 0 ;
        if( ( p = string.indexOf( START_PLIB ) ) <= -1 ) {
            return string ;
        }
        StringBuilder buf = new StringBuilder() ;
        for( ;; ) {
            int e = indexToNotCote( string,END_PLIB,p ) ;
            if( e <= -1 ) {
                buf.append( string.substring( b ) ) ;
                break ;
            }
            buf.append( string.substring( b,p ) ) ;
            buf.append( "<%= " ).append( cutEnter( string.substring( p+2,e ) ) ).append( " %>" ) ;
            // 次の処理を行う準備.
            b = e+1 ;
            p = string.indexOf( START_PLIB,b ) ;
            if( p <= -1 ) {
                buf.append( string.substring( b ) ) ;
                break ;
            }
        }
        return buf.toString() ;
    }
    
    /**
     * 出力タグ(HTML変換版)解析処理.
     */
    private static final String varHtmlLib( String string )
        throws Exception {
        int p = 0 ;
        int b = 0 ;
        if( ( p = string.indexOf( START_PHLIB ) ) <= -1 ) {
            return string ;
        }
        StringBuilder buf = new StringBuilder() ;
        for( ;; ) {
            int e = indexToNotCote( string,END_PHLIB,p ) ;
            if( e <= -1 ) {
                buf.append( string.substring( b ) ) ;
                break ;
            }
            buf.append( string.substring( b,p ) ) ;
            buf.append( "<%= $html(" ).append( cutEnter( string.substring( p+2,e ) ) ).append( ") %>" ) ;
            b = e+1 ;
            p = string.indexOf( START_PHLIB,b ) ;
            if( p <= -1 ) {
                buf.append( string.substring( b ) ) ;
                break ;
            }
        }
        return buf.toString() ;
    }
    
    /**
     * コーテーション内を検知しないIndexOf.
     */
    private static final int indexToNotCote( String base,String cc,int off ) throws Exception {
        int len = base.length() ;
        int cote = -1 ;
        char[] ck = cc.toCharArray() ;
        int cLen = ck.length ;
        char bef = 0 ;
        for( int i = off ; i < len ; i ++ ) {
            char c = base.charAt( i ) ;
            if( cote != -1 ) {
                if( bef != '\\' && c == cote ) {
                    cote = -1 ;
                }
            }
            else {
                if( bef != '\\' && ( c == '\'' || c == '\"' ) ) {
                    cote = c ;
                }
                else if( c == ck[ 0 ] ) {
                    boolean res = true ;
                    for( int j = 1 ; j < cLen ; j ++ ) {
                        if( i + j >= len || ck[ j ] != base.charAt( i + j ) ) {
                            res = false ;
                            break ;
                        }
                    }
                    if( res == true ) {
                        return i ;
                    }
                }
            }
            bef = c ;
        }
        return -1 ;
    }
    
    /**
     * カッコを検知しないIndexOf.
     */
    private static final int indexToNotPar( String base,String cc,char st,char ed,int off ) throws Exception {
        int len = base.length() ;
        int par = -1 ;
        char[] ck = cc.toCharArray() ;
        int cLen = ck.length ;
        int idx = 0 ;
        for( int i = off ; i < len ; i ++ ) {
            char c = base.charAt( i ) ;
            if( par != -1 ) {
                if( c == ed ) {
                    idx -- ;
                    if( idx <= 0 ) {
                        par = -1 ;
                    }
                }
                else if( c == st ) {
                    idx ++ ;
                }
            }
            else {
                if( c == st ) {
                    idx = 1 ;
                    par = c ;
                }
                else if( c == ck[ 0 ] ) {
                    boolean res = true ;
                    for( int j = 1 ; j < cLen ; j ++ ) {
                        if( i + j >= len || ck[ j ] != base.charAt( i + j ) ) {
                            res = false ;
                            break ;
                        }
                    }
                    if( res == true ) {
                        return i ;
                    }
                }
            }
        }
        return -1 ;
    }
    
    /**
     * 前後のスペース等を取り除く.
     */
    private static final String trimPlus( String string ) {
        int s = -1 ;
        int e = -1 ;
        int len = string.length() ;
        for( int i = 0 ; i < len ; i ++ ) {
            char c = string.charAt( i ) ;
            if( c != ' '  && c != '　' && c != '\r' && c != '\n' && c != '\t' ) {
                s = i ;
                break ;
            }
        }
        if( s == -1 ) {
            return "" ;
        }
        for( int i = len-1 ; i >= 0 ; i -- ) {
            char c = string.charAt( i ) ;
            if( c != ' '  && c != '　' && c != '\r' && c != '\n' && c != '\t' ) {
                e = i ;
                break ;
            }
        }
        return string.substring( s,e+1 ) ;
    }
    
    /**
     * 文字列内の改行を取り除く.
     */
    private static final String cutEnter( String string ) {
        if( string == null || ( string = trimPlus( string ) ).length() <= 0 ) {
            return "" ;
        }
        StringBuilder buf = new StringBuilder() ;
        int len = string.length() ;
        int cote = -1 ;
        for( int i = 0 ; i < len ; i ++ ) {
            char c = string.charAt( i ) ;
            if( cote != -1 ) {
                if( cote == c ) {
                    cote = -1 ;
                }
            }
            else if( c == '\'' || c == '\"' ) {
                cote = c ;
            }
            if( c == '\r' || c == '\n' ) {
                if( cote == -1 ) {
                    buf.append( " " ) ;
                }
                continue ;
            }
            buf.append( c ) ;
        }
        return buf.toString() ;
    }
    
    /**
     * タグライブラリ名を取得.
     */
    private static final String getTagLibName( int[] out,String string,int pos )
        throws Exception {
        boolean endFlag = false ;
        int s = -1 ;
        int len = string.length() ;
        for( int i = pos+2 ; i < len ; i ++ ) {
            char c = string.charAt( i ) ;
            if( c != ' '  && c != '　' && c != '\r' && c != '\n' && c != '\t' ) {
                if( c == '/' ) {
                    endFlag = true ;
                    continue ;
                }
                s = i ;
                break ;
            }
        }
        if( s == -1 ) {
            throw new IOException( "不正なタグ条件が存在します:pos=" + pos ) ;
        }
        int p = -1 ;
        for( int i = s ; i < len ; i ++ ) {
            char c = string.charAt( i ) ;
            if( c == ' '  || c == '　' || c == '\r' || c == '\n' || c == '\t' || c == '(' || c == '>' ) {
                p = i ;
                break ;
            }
        }
        if( p == -1 ) {
            throw new IOException( "不正なタグ条件が存在します:pos=" + pos ) ;
        }
        out[ 0 ] = p ;
        if( endFlag == true ) {
            return "/" + string.substring( s,p ).toLowerCase() ;
        }
        return string.substring( s,p ).toLowerCase() ;
    }
    
    /**
     * タグ以外の文字列を取得.
     */
    private static final String cutTagAndTagName( boolean mode,String string,int p,int e )
        throws Exception {
        String s = string.substring( p,e ) ;
        if( mode == true ) {
            return trimPlus( s ) ;
        }
        else {
            return cutEnter( s ) ;
        }
    }
    
    /**
     * 予約語判別処理.
     */
    private static final int parCode( String tagName ) {
        if( "if".equals( tagName ) ) {
            return 1 ;
        }
        if( "elseif".equals( tagName ) ) {
            return 2 ;
        }
        if( "for".equals( tagName ) ) {
            return 3 ;
        }
        if( "while".equals( tagName ) ) {
            return 4 ;
        }
        if( "switch".equals( tagName ) ) {
            return 5 ;
        }
        if( "else".equals( tagName ) ) {
            return 6 ;
        }
        if( "case".equals( tagName ) ) {
            return 7 ;
        }
        if( "/endif".equals( tagName ) || "/for".equals( tagName ) ||
            "/while".equals( tagName ) || "/switch".equals( tagName ) ) {
            return 8 ;
        }
        if( "/case".equals( tagName ) ) {
            return 9 ;
        }
        if( "default".equals( tagName ) ) {
            return 10 ;
        }
        if( "/default".equals( tagName ) ) {
            return 11 ;
        }
        return -1 ;
    }
    
    /**
     * 予約語でタグライブラリを処理する.
     */
    private static final void tagLibToPar( StringBuilder buf,String string,String tagName,int parCd,int p,int e )
        throws Exception {
        String inner = cutTagAndTagName( true,string,p,e ) ;
        switch( parCd ) {
            case 1 :
            case 2 :
            case 3 :
            case 4 :
            case 5 :
                if( inner.startsWith( "(" ) == false || inner.endsWith( ")" ) == false ) {
                    throw new IOException( "タグ(" + tagName + ")に対して不正な内容が設定されています:" + inner ) ;
                }
                buf.append( "<% " ) ;
                if( parCd == 1 ) {
                    buf.append( "if " ) ;
                }
                if( parCd == 2 ) {
                    buf.append( "} else if " ) ;
                }
                if( parCd == 3 ) {
                    buf.append( "for " ) ;
                }
                if( parCd == 4 ) {
                    buf.append( "while " ) ;
                }
                if( parCd == 5 ) {
                    buf.append( "switch " ) ;
                }
                buf.append( inner ) ;
                buf.append( " { %>" ) ;
                break ;
            case 6 :
                buf.append( "<% } else { %>" ) ;
                break ;
            case 7 :
                buf.append( "<% case : " ).append( inner ).append( " %>" ) ;
                break ;
            case 8 :
                buf.append( "<% } %>" ) ;
                break ;
            case 9 :
                buf.append( "<% break ; %>" ) ;
                break ;
            case 10 :
                buf.append( "<% default : %>" ) ;
                break ;
            case 11 :
                buf.append( "<% break ; %>" ) ;
                break ;
        }
    }
    
    /**
     * 指定タグに対する引数長を取得.
     */
    private static final int getTagLibArgsLength( Config conf,String tagName ) {
        String[] keys = conf.getKeys( tagName ) ;
        int len = keys.length ;
        int ret = 0 ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( keys[ i ].startsWith( "$" ) ) {
                ret ++ ;
            }
        }
        return ret ;
    }
    
    /**
     * ターゲットとなるタグライブラリを処理する.
     */
    private static final void tagLibToDef( StringBuilder buf,String string,String tagName,Config conf,int p,int e )
        throws Exception {
        boolean notPrint = conf.getBoolean( tagName,"notPrint",0 ) ;
        int argsLen = getTagLibArgsLength( conf,tagName ) ;
        if( argsLen <= 0 ) {
            if( notPrint ) {
                buf.append( "<% " ) ;
            }
            else {
                buf.append( "<%= " ) ;
            }
            buf.append( conf.get( tagName,"function",0 ) ).append( "() %>" ) ;
            return ;
        }
        String inner = cutTagAndTagName( false,string,p,e ) ;
        ArrayList<String> lst = new ArrayList<String>() ;
        //StringUtil.cutString( lst,true,true,inner,"\r\n 　\t=" ) ;
        cutElement( lst,inner ) ;
        ArrayList<String> etc = null ;
        if( conf.isKeys( tagName,"$etc" ) ) {
            etc = new ArrayList<String>() ;
        }
        String[] args = new String[ argsLen ] ;
        
        int len = lst.size() ;
        for( int i = 0 ; i < len ; i += 2 ) {
            String key = lst.get( i ).toLowerCase() ;
            String value = lst.get( i+1 ) ;
            if( "etc".equals( key ) ) {
                continue ;
            }
            else if( DefineTagLib.getInstance().isEvent( key ) ) {
                if( etc != null ) {
                    etc.add( new StringBuilder().append( "Event." ).append( key ).append( "(" ).append( value ).append( ")" ).toString() ) ;
                }
            }
            else {
                int n = conf.getInt( tagName,"$"+key,0 ) ;
                if( n <= -1 ) {
                    if( etc != null ) {
                        // コーテーションが付加されている.
                        if( ( value.startsWith( "\"" ) || value.startsWith( "\'" ) ) &&
                            ( value.endsWith( "\"" ) || value.endsWith( "\'" ) ) ) {
                            char cote ;
                            if( value.startsWith( "\"" ) ) {
                                cote = '\"' ;
                            }
                            else {
                                cote = '\'' ;
                            }
                            value = JsDef.convetByCote( true,
                                new StringBuilder().append( cote ).
                                append( value.substring( 1,value.length()-1 ) ).
                                append( cote ).toString() ) ;
                        }
                        // コーテーションが付加されていない.
                        else {
                            value = JsDef.convetByCote( true,
                                    new StringBuilder().append( "\"" ).
                                    append( value ).
                                    append( "\"" ).toString() ) ;
                        }
                        String v = new StringBuilder().append( "\"" ).append( key ).
                            append( "=" ).append( value ).append( "\"" ).toString() ;
                        etc.add( v ) ;
                    }
                }
                else {
                    args[ n-1 ] = value ;
                }
            }
        }
        if( etc != null && etc.size() > 0 ) {
            len = etc.size() ;
            StringBuilder etcBuf = new StringBuilder() ;
            etcBuf.append( "[ " ) ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( i != 0 ) {
                    etcBuf.append( "," ) ;
                }
                etcBuf.append( etc.get( i ) ) ;
            }
            etcBuf.append( " ]" ) ;
            args[ conf.getInt( tagName,"$etc",0 )-1 ] = etcBuf.toString() ;
        }
        if( notPrint ) {
            buf.append( "<% " ) ;
        }
        else {
            buf.append( "<%= " ) ;
        }
        buf.append( conf.get( tagName,"function",0 ) ).append( "(" ) ;
        len = args.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( i != 0 ) {
                buf.append( "," ) ;
            }
            if( args[ i ] == null ) {
                buf.append( "null" ) ;
            }
            else {
                buf.append( args[ i ] ) ;
            }
        }
        buf.append( ") %>" ) ;
    }
    
    /**
     * 要素内容を区切る.
     */
    private static final void cutElement( ArrayList<String> out,String string )
        throws Exception {
        int len = string.length() ;
        int state = 0 ;
        int par = -1 ;
        int parCnt = 0 ;
        int cote = -1 ;
        int s = -1 ;
        int e = -1 ;
        int bf = -1 ;
        for( int i = 0 ; i < len ; i ++ ) {
            char c = string.charAt( i ) ;
            // コーテーション内の条件.
            if( cote != -1 ) {
                if( c == cote && bf != '\\' ) {
                    cote = -1 ;
                }
            }
            // カッコ内の条件.
            else if( par != -1 ) {
                if( c == '(' ) {
                    parCnt ++ ;
                }
                else if( c == ')' ) {
                    parCnt -- ;
                }
                if( parCnt == 0 ) {
                    parCnt = 0 ;
                    par = -1 ;
                }
            }
            else {
                if( c == '\'' || c == '\"' ) {
                    if( s != -1 ) {
                        throw new IOException( "不正な要素内容が存在します[" + string + "]" ) ;
                    }
                    else if( state == 0 ) {
                        throw new IOException( "不正な要素内容が存在します[" + string + "]" ) ;
                    }
                    else if( bf == '\\' ) {
                        throw new IOException( "不正なコーテーション内容が存在します[" + string + "]" ) ;
                    }
                    cote = c ;
                    s = i ;
                    continue ;
                }
                else if( c == '(' ) {
                    if( s != -1 ) {
                        throw new IOException( "不正な要素内容が存在します[" + string + "]" ) ;
                    }
                    else if( state == 0 ) {
                        throw new IOException( "不正な要素内容が存在します[" + string + "]" ) ;
                    }
                    par = c ;
                    parCnt = 1 ;
                    s = i ;
                    continue ;
                }
                else if( c == '\r' || c == '\n' || c == ' ' || c == '　' || c == '\t' ) {
                    if( state == 0 && s != -1 && e == -1 ) {
                        e = i ;
                    }
                    else if( state == 1 && s != -1 ) {
                        out.add( string.substring( s,i ) ) ;
                        state = 0 ;
                        s = -1 ;
                        e = -1 ;
                    }
                }
                else if( c == '=' ) {
                    if( state == 0 && s != -1 ) {
                        if( e == -1 ) {
                            e = i ;
                        }
                        out.add( string.substring( s,e ) ) ;
                        state = 1 ;
                        s = -1 ;
                        e = -1 ;
                    }
                }
                else if( s == -1 ) {
                    s = i ;
                }
                else if( e != -1 ) {
                    throw new IOException( "不正な要素内容が存在します[" + string + "]" ) ;
                }
            }
            bf = ( int )( c & 0x0000ffff ) ;
        }
        if( state == 0 && s != -1 ) {
            throw new IOException( "不正な要素内容が存在します[" + string + "]" ) ;
        }
        else if( state == 1 && s != -1 ) {
            out.add( string.substring( s ) ) ;
        }
    }
}

