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.comet.httpd.engine.script.scripts.ReadScriptPlus;
import org.maachang.conf.Config;
import org.maachang.util.StringUtil;

/**
 * タグライブラリ解析処理.
 * 
 * @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 = "${" ;
    
    /**
     * パラメータ出力開始タグ(HTML変換版).
     */
    private static final String START_PHLIB = "@{" ;
    
    /**
     * パラメータ出力開始タグ(円変換版).
     */
    private static final String START_PYLIB = "#{" ;
    
    /**
     * コメント開始.
     */
    private static final String COMMENT_START = "/*" ;
    
    /**
     * コメント終了.
     */
    private static final String COMMENT_END = "*/" ;
    
    /**
     * タグライブラリ解析.
     * @param string 解析内容を設定します.
     * @return String 解析結果が返されます.
     * @exception Exception 例外.
     */
    public static final String analysis( String string )
        throws Exception {
        string = tagLib( string ) ;
        string = varLib( string ) ;
        string = varHtmlLib( string ) ;
        string = varYenLib( string ) ;
        string = varComment( string ) ;
        return 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 = StringUtil.indexToNotPar( string,END_TAGLIB,'(',')',p ) ;
                if( e <= -1 ) {
                    buf.append( string.substring( b ) ) ;
                    break ;
                }
                // 予約語で処理する.
                parFlag = true ;
            }
            // タグ名が予約語でない場合.
            else {
                e = StringUtil.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 = StringUtil.indexParAndCote( string,'{','}',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 = StringUtil.indexParAndCote( string,'{','}',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() ;
    }
    
    /**
     * 出力タグ(円変換版)解析処理.
     */
    private static final String varYenLib( String string )
        throws Exception {
        int p = 0 ;
        int b = 0 ;
        if( ( p = string.indexOf( START_PYLIB ) ) <= -1 ) {
            return string ;
        }
        StringBuilder buf = new StringBuilder() ;
        for( ;; ) {
            int e = StringUtil.indexParAndCote( string,'{','}',p ) ;
            if( e <= -1 ) {
                buf.append( string.substring( b ) ) ;
                break ;
            }
            buf.append( string.substring( b,p ) ) ;
            buf.append( "<%= formatYen(" ).append( cutEnter( string.substring( p+2,e ) ) ).append( ") %>" ) ;
            b = e+1 ;
            p = string.indexOf( START_PYLIB,b ) ;
            if( p <= -1 ) {
                buf.append( string.substring( b ) ) ;
                break ;
            }
        }
        return buf.toString() ;
    }
    
    /**
     * コメント解析処理.
     */
    private static final String varComment( String string )
        throws Exception {
        int p = 0 ;
        int b = 0 ;
        if( ( p = string.indexOf( COMMENT_START ) ) <= -1 ) {
            return string ;
        }
        StringBuilder buf = new StringBuilder() ;
        for( ;; ) {
            int e = StringUtil.indexToNotCote( string,COMMENT_END,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+2 ;
            p = string.indexOf( COMMENT_START,b ) ;
            if( p <= -1 ) {
                buf.append( string.substring( b ) ) ;
                break ;
            }
        }
        return buf.toString() ;
    }
    
    /**
     * 文字列内の改行を取り除く.
     */
    private static final String cutEnter( String string ) {
        if( string == null || ( string = StringUtil.trim( 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 StringUtil.trim( 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 ) || "/endif".equals( tagName ) ||
            "/for".equals( tagName ) || "/while".equals( tagName ) ||
            "/switch".equals( tagName ) ||
            "/try".equals( tagName ) || "/catch".equals( tagName ) ) {
            return 8 ;
        }
        if( "/case".equals( tagName ) ) {
            return 9 ;
        }
        if( "default".equals( tagName ) ) {
            return 10 ;
        }
        if( "/default".equals( tagName ) ) {
            return 11 ;
        }
        if( "server".equals( tagName ) ) {
            return 12 ;
        }
        if( "/server".equals( tagName ) ) {
            return 13 ;
        }
        if( "direct".equals( tagName ) ) {
            return 14 ;
        }
        if( "try".equals( tagName ) ) {
            return 15 ;
        }
        if( "catch".equals( tagName ) ) {
            return 16 ;
        }
        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 :
            case 16 :
                if( inner.startsWith( "(" ) == false || inner.endsWith( ")" ) == false ) {
                    throw new IOException( "タグ(" + tagName + ")に対して不正な内容が設定されています:" + inner ) ;
                }
                buf.append( "<% " ) ;
                if( parCd == 1 ) {
                    buf.append( "if " ) ;
                }
                else if( parCd == 2 ) {
                    buf.append( "} else if " ) ;
                }
                else if( parCd == 3 ) {
                    buf.append( "for " ) ;
                }
                else if( parCd == 4 ) {
                    buf.append( "while " ) ;
                }
                else if( parCd == 5 ) {
                    buf.append( "switch " ) ;
                }
                else if( parCd == 16 ) {
                    buf.append( "catch " ) ;
                }
                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 ;
            case 12 :
                buf.append( "<%" ) ;
                break ;
            case 13 :
                buf.append( "%>" ) ;
                break ;
            case 14 :
                // ダイレクトインクルード.
                {
                    ArrayList<String> lst = new ArrayList<String>() ;
                    cutElement( lst,inner ) ;
                    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( key.equals( "src" ) ) {
                            value = StringUtil.cutCote( value ) ;
                            String s = ReadScriptPlus.getInnerFile( "./",value ) ;
                            if( s != null && s.length() > 0 ) {
                                buf.append( AnalysisTagLib.analysis( s ) ) ;
                                break ;
                            }
                        }
                    }
                }
                break ;
            case 15 :
                buf.append( "<% try { %>" ) ;
                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>() ;
        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 ) ;
            boolean parseValueFlag = isParseValueByKey( key,value ) ;
            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 if( parseValueFlag ) {
                    args[ n-1 ] = new StringBuilder().
                        append( "parseValue(" ).append( value ).append( ")" ).toString() ;
                }
                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 {
        if( string == null || ( string = string.trim() ).length() <= 0 ) {
            return ;
        }
        int len = string.length() ;
        int state = 0 ;
        int par = -1 ;
        int parCnt = 0 ;
        int par2 = -1 ;
        int par2Cnt = 0 ;
        int par3 = -1 ;
        int par3Cnt = 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( par2 != -1 ) {
                if( c == '[' ) {
                    par2Cnt ++ ;
                }
                else if( c == ']' ) {
                    par2Cnt -- ;
                }
                if( par2Cnt == 0 ) {
                    par2Cnt = 0 ;
                    par2 = -1 ;
                }
            }
            // {}カッコ内の条件.
            else if( par3 != -1 ) {
                if( c == '{' ) {
                    par3Cnt ++ ;
                }
                else if( c == '}' ) {
                    par3Cnt -- ;
                }
                if( par3Cnt == 0 ) {
                    par3Cnt = 0 ;
                    par3 = -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 == '[' ) {
                    if( s != -1 ) {
                        throw new IOException( "不正な要素内容が存在します[" + string + "]" ) ;
                    }
                    else if( state == 0 ) {
                        throw new IOException( "不正な要素内容が存在します[" + string + "]" ) ;
                    }
                    par2 = c ;
                    par2Cnt = 1 ;
                    s = i ;
                    continue ;
                }
                else if( c == '{' ) {
                    if( s != -1 ) {
                        throw new IOException( "不正な要素内容が存在します[" + string + "]" ) ;
                    }
                    else if( state == 0 ) {
                        throw new IOException( "不正な要素内容が存在します[" + string + "]" ) ;
                    }
                    par3 = c ;
                    par3Cnt = 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 ) ) ;
        }
    }
    
    /**
     * parseValueを付加するキーであるかチェック.
     */
    private static final boolean isParseValueByKey( String key,String value ) {
        if( value.startsWith( "(" ) || value.startsWith( "[" ) || value.startsWith( "{" ) ) {
            return false ;
        }
        return( "name".equals( key ) ||
            "action".equals( key ) ||
            "src".equals( key ) ||
            "href".equals( key ) ||
            "mime".equals( key ) ||
            "alt".equals( key ) ||
            "params".equals( key ) ||
            "form".equals( key ) ||
            "action".equals( key ) ||
            "master".equals( key ) ||
            "column".equals( key ) ||
            "eof".equals( key ) ||
            "header".equals( key ) ||
            "fooder".equals( key ) ||
            key.indexOf( "class" ) != -1 ||
            key.indexOf( "view" ) != -1 ) ? false : true ;
    }
}

