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

import java.util.ArrayList;

import org.maachang.comet.httpd.HttpdErrorDef;
import org.maachang.comet.httpd.HttpdStateException;
import org.maachang.util.StringUtil;

/**
 * テンプレート用スクリプト解析.
 * 
 * @version 2007/08/29
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class TemplateScriptAnalysis {
    
    /**
     * スクリプトタグ : 開始.
     */
    private static final String START_SCRIPT = "<%" ;
    
    /**
     * スクリプトタグ : 終了.
     */
    private static final String END_SCRIPT = "%>" ;
    
    /**
     * スクリプトタグ : 代入開始.
     */
    private static final String START_SCRIPT_EQUALS = "<%=" ;
    
    /**
     * スクリプトタグ : コメント.
     */
    private static final String START_SCRIPT_COMMENT = "<%#" ;
    
    /**
     * 改行情報.
     */
    private static final String ENTER = "\n" ;
    
    /**
     * 出力メソッド.
     */
    private static final String DEFAULT_OUTPUT = "httpPrint" ;
    
    /**
     * テンプレート情報を解析.
     * <BR><BR>
     * 指定情報のテンプレートスクリプトを解析します.
     * <BR>
     * @param template 対象のテンプレートを設定します.
     * @return String テンプレート解析結果が返されます.
     */
    public static final String analysis( String template )
        throws Exception {
        return analysis( DEFAULT_OUTPUT,template ) ;
    }
    
    /**
     * テンプレート情報を解析.
     * <BR><BR>
     * 指定情報のテンプレートスクリプトを解析します.
     * <BR>
     * @param outputMethod テンプレートデータ出力先情報を設定します.
     * @param template 対象のテンプレートを設定します.
     * @return String テンプレート解析結果が返されます.
     */
    public static final String analysis( String outputMethod,String template )
        throws Exception {
        if( template == null || ( template = template.trim() ).length() <= 0 ) {
            return "" ;
        }
        if( outputMethod == null || ( outputMethod = outputMethod.trim() ).length() <= 0 ) {
            outputMethod = DEFAULT_OUTPUT ;
        }
        return convertTemplateByScript( "\\n",template,null,null,outputMethod ) ;
    }
    
    /**
     * テンプレート情報を解析.
     * <BR><BR>
     * 指定情報のテンプレートスクリプトを解析します.
     * <BR>
     * @param enterTo 置き換え改行コードを設定します.
     * @param header ヘッダ出力情報を設定します.
     * @param fooder フッダ出力情報を設定します.
     * @param outputMethod テンプレートデータ出力先情報を設定します.
     * @param template 対象のテンプレートを設定します.
     * @return String テンプレート解析結果が返されます.
     */
    public static final String analysis( String enterTo,String header,String fooder,String outputMethod,String template )
        throws Exception {
        if( template == null || ( template = template.trim() ).length() <= 0 ||
            outputMethod == null || ( outputMethod = outputMethod.trim() ).length() <= 0 ) {
            return "" ;
        }
        if( enterTo == null || ( enterTo = enterTo.trim() ).length() <= 0 ) {
            enterTo = "" ;
        }
        return convertTemplateByScript( enterTo,template,header,fooder,outputMethod ) ;
    }
    
    /**
     * テンプレートをスクリプトに変換.
     */
    private static final String convertTemplateByScript( String enterTo,String html,String header,String fooder,String outputMethod )
        throws Exception {
        // コメント行を除外.
        html = cutComment( html ) ;
        // 改行を統一.
        html = convertEnter( html ) ;
        StringBuilder buf = new StringBuilder( ( int )( html.length() * 1.5f ) ) ;
        if( header != null ) {
            buf.append( header ) ;
            buf.append( ENTER ) ;
        }
        int p = 0 ;
        int b = 0 ;
        int t = -1 ;
        if( ( p = html.indexOf( START_SCRIPT ) ) == -1 ) {
            pushEnter( buf,enterTo,html,outputMethod ) ;
        }
        else {
            // HTMLの初めに、スクリプトタグが存在しない場合.
            if( p != 0 ) {
                // html.
                String s = html.substring( 0,p ) ;
                if( s != null && s.length() > 0 ) {
                    pushEnter( buf,enterTo,s,outputMethod ) ;
                }
            }
            for( ;; ) {
                b = p ;
                // 最後の情報である場合.
                if( ( p = html.indexOf( END_SCRIPT,b ) ) == -1 ) {
                    // last html.
                    String s = html.substring( b+END_SCRIPT.length(),html.length() ) ;
                    if( s != null && s.length() > 0 ) {
                        pushEnter( buf,enterTo,s,outputMethod ) ;
                    }
                    break ;
                }
                // 開始位置と終了位置の間に開始位置が存在する場合.
                if( ( t = html.indexOf( START_SCRIPT,b+END_SCRIPT.length() ) ) != -1 && t < p ) {
                    throw new HttpdStateException( HttpdErrorDef.HTTP11_500,
                        "不正なスクリプト開始位置を検知しました[start:" + b + " pos:" + p +
                        " string[S]:" + StringUtil.getNLength( html,b,30 ) +
                        " string[E]:" + StringUtil.getNLength( html,p,30 ) +"]" ) ;
                }
                // 開始位置が、代入条件の場合.
                if( html.indexOf( START_SCRIPT_EQUALS,b ) == b ) {
                    // =script.
                    String s = html.substring( b+START_SCRIPT_EQUALS.length(),p ) ;
                    if( s == null || s.length() <= 0 ) {
                        continue ;
                    }
                    s = s.trim() ;
                    if( s.endsWith( ";" ) ) {
                        s = s.substring( 0,s.length()-1 ) ;
                    }
                    buf.append( outputMethod ).append( "(" ).append( cutEnter( enterTo,s ) ).append( ");" ).append( ENTER ) ;
                }
                // スクリプトコメント情報が存在する.
                else if( html.indexOf( START_SCRIPT_COMMENT,b ) == b ) {
                    // comment.
                    buf.append( ENTER ) ;
                }
                // 開始位置が、代入条件でない場合.
                else {
                    // script.
                    String s = html.substring( b+START_SCRIPT.length(),p ) ;
                    if( s == null || s.length() <= 0 ) {
                        continue ;
                    }
                    buf.append( cutEnter( enterTo,s ) ).append( ENTER ) ;
                    s = null ;
                }
                b = p + END_SCRIPT.length() ;
                // 次の開始位置を取得.
                if( ( p = html.indexOf( START_SCRIPT,b ) ) == -1 ) {
                    // last html.
                    String s = html.substring( b,html.length() ) ;
                    if( s != null && s.length() > 0 ) {
                        pushEnter( buf,enterTo,s,outputMethod ) ; ;
                    }
                    break ;
                }
                else {
                    // html.
                    String s = html.substring( b,p ) ;
                    if( s != null && s.length() > 0 ) {
                        pushEnter( buf,enterTo,s,outputMethod ) ;
                    }
                }
            }
        }
        if( fooder != null ) {
            buf.append( ENTER ) ;
            buf.append( fooder ) ;
            buf.append( ENTER ) ;
        }
        //System.out.println( "buf.toString():\n" + buf.toString() ) ;
        return buf.toString() ;
    }
    
    /**
     * 改行情報を外す.
     */
    private static final String cutEnter( String enterTo,String string ) {
        return StringUtil.changeString( string,"\n","" ) ;
    }
    
    /**
     * 改行単位で出力処理.
     */
    private static final void pushEnter( StringBuilder buf,String enterTo,String html,String outputMethod )
        throws Exception {
        ArrayList<String> lst = cutEnterByArray( html ) ;
        if( lst != null && lst.size() > 0 ) {
            int len = lst.size() ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( i != 0 ) {
                    if( enterTo != null && enterTo.length() > 0 ) {
                        buf.append( outputMethod ).append( "(\"" ).append( enterTo ).append( "\");" ).
                            append( ENTER ) ;
                    }
                }
                if( lst.get( i ).length() >= 0 ) {
                    buf.append( outputMethod ).append( "(\"" ).
                        append( convertCote( lst.get( i ) ) ).append( "\");" ).
                        append( ENTER ) ;
                }
            }
        }
    }
    
    /**
     * コメントを除外.
     */
    protected static final String cutComment( String html )
        throws Exception {
        int b = 0 ;
        StringBuilder buf = new StringBuilder() ;
        for( ;; ) {
            int s = html.indexOf( START_SCRIPT,b ) ;
            if( s <= -1 ) {
                buf.append( html.substring( b ) ) ;
                break ;
            }
            int e = html.indexOf( END_SCRIPT,s ) ;
            if( e <= -1 ) {
                buf.append( html.substring( b ) ) ;
                break ;
            }
            buf.append( html.substring( b,s ) ) ;
            if( html.indexOf( START_SCRIPT_COMMENT,b ) == s ) {
                b = e + END_SCRIPT.length() ;
                continue ;
            }
            else if( html.indexOf( START_SCRIPT_EQUALS,b ) == s ) {
                s += START_SCRIPT_EQUALS.length() ;
                buf.append( START_SCRIPT_EQUALS ) ;
            }
            else {
                s += START_SCRIPT.length() ;
                buf.append( START_SCRIPT ) ;
            }
            String in = html.substring( s,e ) ;
            in = ReadScriptPlus.cutComment( in ) ;
            buf.append( in ) ;
            buf.append( END_SCRIPT ) ;
            b = e + END_SCRIPT.length() ;
        }
        return buf.toString() ;
    }
    
    /**
     * コーテーション変換.
     */
    private static final String convertCote( String html ) {
        if( html == null || html.length() <= 0 ) {
            return html ;
        }
        html = StringUtil.changeString( html,"\\\\\"","\\\\\\\"" ) ;
        html = StringUtil.changeString( html,"\\\\\'","\\\\\\\'" ) ;
        html = StringUtil.changeString( html,"\\\"","\\\\\"" ) ;
        html = StringUtil.changeString( html,"\\\'","\\\\\'" ) ;
        html = StringUtil.changeString( html,"\"","\\\"" ) ;
        return StringUtil.changeString( html,"\'","\\\'" ) ;
    }
    
    /**
     * 改行を[\n]に統一.
     */
    private static final String convertEnter( String html ) {
        return StringUtil.changeString( html,"\r","" ) ;
    }
    
    /**
     * 改行コード単位で情報を区切る.
     */
    private static final ArrayList<String> cutEnterByArray( String string ) {
        ArrayList<String> ret = new ArrayList<String>() ;
        if( string.indexOf( "\n" ) == -1 ) {
            ret.add( string ) ;
            return ret ;
        }
        for( int b = 0 ;; ) {
            int p = string.indexOf( "\n",b ) ;
            if( p == -1 ) {
                ret.add( string.substring( b ) ) ;
                break ;
            }
            ret.add( string.substring( b,p ) ) ;
            b = p + 1 ;
        }
        return ret ;
    }
}
