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

import org.maachang.comet.MaachangDef;
import org.maachang.comet.httpd.engine.HttpdDef;
import org.maachang.comet.httpd.engine.script.ScriptDef;
import org.maachang.comet.httpd.engine.script.js.InitJS;
import org.maachang.util.FileUtil;
import org.maachang.util.StringUtil;

/**
 * 読み込みスクリプトに対して基本追加する条件.
 * 
 * @version 2008/05/21
 * @author masahito suzuki
 * @since MaachangComet 1.16
 */
public class ReadScriptPlus {
    
    private ReadScriptPlus() {
    }
    
    /**
     * cacheに対するexitチェック.
     */
    private static final String[] CACHE_BY_EXIT = {
        "catch","{","_isExitTo();" } ;
    
    /**
     * 読み込まれたスクリプトを設定.
     * @param comment [false]の場合、コメント除外処理は実施しません.
     * @param script 対象のスクリプトを設定します.
     * @return String 変換された情報が返されます.
     * @exception Exception 例外.
     */
    public static final String convert( boolean comment,String script ) throws Exception {
        if( script == null || ( script = script.trim() ).length() <= 0 ) {
            return "" ;
        }
        if( comment == true ) {
            script = cutComment( script ) ;
        }
        // cacheに対するexitチェックを行う処理を付加.
        script = add( script,CACHE_BY_EXIT[0],CACHE_BY_EXIT[1],CACHE_BY_EXIT[2] ) ;
        return script ;
    }
    
    /**
     * コメント行を省いた情報を取得.
     */
    protected static final String cutComment( String script ) throws Exception {
        if( script == null || ( script = script.trim() ).length() <= 0 ) {
            return "" ;
        }
        StringBuffer buf = new StringBuffer() ;
        int len = script.length() ;
        int cote = -1 ;
        int commentType = -1 ;
        int bef = -1 ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( i != 0 ) {
                bef = script.charAt( i-1 ) ;
            }
            char c = script.charAt(i) ;
            // シングル／ダブルコーテーション内の処理.
            if( cote != -1 ) {
                if( c == cote ) {
                    if( ( char )bef != '\\' ) {
                        cote = -1 ;
                    }
                }
                buf.append( c ) ;
                continue ;
            }
            // コメント内の処理.
            else if( commentType != -1 ) {
                if( commentType == 1 ) {
                    if( c == '\n' || c == '\r' ) {
                        if( c == '\r' ) {
                            if( len > i + 1 && script.charAt( i+1 ) == '\n' ) {
                                i ++ ;
                                buf.append( "\r\n" ) ;
                            }
                            else {
                                buf.append( c ) ;
                            }
                        }
                        else {
                            buf.append( c ) ;
                        }
                        commentType = -1 ;
                    }
                }
                else if( commentType == 2 ) {
                    if( c == '*' ) {
                        if( len > i + 1 && script.charAt( i+1 ) == '/' ) {
                            i ++ ;
                            commentType = -1 ;
                        }
                    }
                }
                continue ;
            }
            // それ以外の処理.
            else {
                if( c == '/' ) {
                    if( len <= i + 1 ) {
                        buf.append( c ) ;
                        continue ;
                    }
                    char c2 = script.charAt( i+1 ) ;
                    if( c2 == '*' ) {
                        commentType = 2 ;
                        continue ;
                    }
                    else if( c2 == '/' ) {
                        commentType = 1 ;
                        continue ;
                    }
                }
                else if( c == '\'' || c == '\"' ) {
                    cote = ( int )( c & 0x0000ffff ) ;
                }
                buf.append( c ) ;
            }
        }
        return buf.toString() ;
    }
    
    /**
     * コーテーション以外での、指定文字列が一致する条件に対して、
     * 条件を付加.
     */
    private static final String add( String script,String main,String sub,String inc )
        throws Exception {
        if( script == null || ( script = script.trim() ).length() <= 0 ) {
            return "" ;
        }
        StringBuffer buf = new StringBuffer() ;
        int len = script.length() ;
        int cote = -1 ;
        int startPos = -1 ;
        boolean target = false ;
        char[] mainChars = main.toCharArray() ;
        char[] subChars = null ;
        if( sub != null && sub.length() > 0 ) {
            subChars = sub.toCharArray() ;
        }
        int bef = -1 ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( i != 0 ) {
                bef = script.charAt( i-1 ) ;
            }
            char c = script.charAt(i) ;
            // シングル／ダブルコーテーション内の処理.
            if( cote != -1 ) {
                if( c == cote ) {
                    if( ( char )bef != '\\' ) {
                        cote = -1 ;
                    }
                }
                buf.append( c ) ;
                startPos = -1 ;
                continue ;
            }
            // チェックターゲットの処理.
            else if( target == true ) {
                if( subChars == null ) {
                    buf.append( inc ) ;
                    buf.append( c ) ;
                    startPos = -1 ;
                    target = false ;
                    continue ;
                }
                // コメント開始.
                else if( c == '\'' || c == '\"' ) {
                    cote = ( int )( c & 0x0000ffff ) ;
                    buf.append( c ) ;
                    startPos = -1 ;
                    continue ;
                }
                // sub開始チェック.
                else if( startPos == -1 ) {
                    if( subChars[ 0 ] == c ) {
                        startPos = 1 ;
                        if( startPos >= subChars.length ) {
                            buf.append( c ) ;
                            buf.append( inc ) ;
                            startPos = -1 ;
                            target = false ;
                            continue ;
                        }
                    }
                }
                // sub開始チェック後のチェック.
                else {
                    if( subChars[ startPos ] == c ) {
                        startPos ++ ;
                        if( startPos >= subChars.length ) {
                            buf.append( c ) ;
                            buf.append( inc ) ;
                            startPos = -1 ;
                            target = false ;
                            continue ;
                        }
                    }
                    else {
                        startPos = -1 ;
                    }
                }
                buf.append( c ) ;
            }
            // 通常のチェック処理.
            else {
                // コメント開始.
                if( c == '\'' || c == '\"' ) {
                    cote = ( int )( c & 0x0000ffff ) ;
                    buf.append( c ) ;
                    startPos = -1 ;
                    continue ;
                }
                // main開始チェック.
                if( startPos == -1 ) {
                    if( mainChars[ 0 ] == c ) {
                        startPos = 1 ;
                        if( startPos >= mainChars.length ) {
                            startPos = -1 ;
                            target = true ;
                        }
                    }
                }
                // main開始チェック後のチェック.
                else {
                    if( mainChars[ startPos ] == c ) {
                        startPos ++ ;
                        if( startPos >= mainChars.length ) {
                            startPos = -1 ;
                            target = true ;
                        }
                    }
                    else {
                        startPos = -1 ;
                    }
                }
                buf.append( c ) ;
            }
        }
        return buf.toString() ;
    }
    
    /**
     * MHTML用追加情報.
     * @param buf 対象のスクリプトバッファを設定します.
     * @param script 対象のスクリプトを設定します.
     * @param path 対象のパスを設定します.
     * @exception Exception 例外.
     */
    public static final void addMHTML( StringBuilder buf,String script,String path ) throws Exception {
        buf.append( ScriptDef.SCRIPT_LAST_MHTML ).append( "=\"" ).append( path ).append( "\";\n" ) ;
        buf.append( "setContentType(\"" ).append( HttpdDef.MHTML_MIME_TYPE ).append( "\");" ).append( "\n" ) ;
        buf.append( InitJS.getInstance().get() ) ;
        buf.append( script ) ;
        buf.append( "_exit(" ).append( "\"exit-script\"" ).append( ") ;" ).append( "\n" ) ;
    }
    
    
    private static final String DIRECT_INCLUDE = "@include" ;
    
    /**
     * ダイレクトインクルード処理.
     * @param currentDir 対象のカレントディレクトリ名を設定します.
     * @param script 対象のスクリプトを設定します.
     * @return String スクリプト内容が返されます.
     * @exception Exception 例外.
     */
    public static final String directInclude( String currentDir,String script )
        throws Exception {
        if( currentDir == null || ( currentDir = currentDir.trim() ).length() <= 0 ||
            script == null || script.length() <= 0 ) {
            return script ;
        }
        int b = 0 ;
        int p = StringUtil.indexToNotCote( script,DIRECT_INCLUDE,b ) ;
        if( p <= -1 ) {
            return script ;
        }
        int incLen = DIRECT_INCLUDE.length() ;
        StringBuilder buf = new StringBuilder() ;
        for( int i = 0 ;; i ++ ) {
            if( i != 0 ) {
                p = StringUtil.indexToNotCote( script,DIRECT_INCLUDE,b ) ;
                if( p <= -1 ) {
                    buf.append( script.substring( b ) ) ;
                    break ;
                }
            }
            int cst = StringUtil.indexToNotCote( script,"(",p ) ;
            if( cst <= -1 ) {
                buf.append( script.substring( b,p ) ) ;
                b = p+incLen ;
                continue ;
            }
            int ced = StringUtil.indexParAndCote( script,'(',')',p ) ;
            if( ced <= -1 ) {
                buf.append( script.substring( b,p ) ) ;
                b = p+incLen ;
                continue ;
            }
            int ed = StringUtil.indexToNotCote( script,";",ced ) ;
            int eed = StringUtil.indexToNotCote( script,"\n",ced ) ;
            if( ed <= -1 || ed > eed ) {
                ed = eed ;
            }
            if( ed <= -1 ) {
                ed = script.length() ;
            }
            String name = StringUtil.trim( script.substring( cst+1,ced ) ) ;
            if( name == null || name.length() <= 0 ) {
                buf.append( script.substring( b,p ) ) ;
                b = ed+1 ;
                continue ;
            }
            if( StringUtil.isCote( name ) == false ) {
                buf.append( script.substring( b,p ) ) ;
                b = ed+1 ;
                continue ;
            }
            name = StringUtil.cutCote( name ) ;
            String inner = getInnerFile( currentDir,name ) ;
            if( inner == null ) {
                buf.append( script.substring( b,p ) ) ;
                b = ed+1 ;
                continue ;
            }
            buf.append( script.substring( b,p ) ) ;
            buf.append( inner ) ;
            b = ed+1 ;
            if( b >= script.length() ) {
                break ;
            }
        }
        return buf.toString() ;
    }
    
    /**
     * Innerファイル内容を取得.
     * @param currentDirectory 対象のカレントディレクトリ名を設定します.
     * @param path 呼び出し元のパスを設定します.
     * @return String Innerファイル内容が返されます.
     * @exception Exception 例外.
     */
    public static final String getInnerFile( String currentDirectory,String path )
        throws Exception {
        if( path.endsWith( ScriptManager.SCRIPT_PLUS ) == false ) {
            path += ScriptManager.SCRIPT_PLUS ;
        }
        currentDirectory = ScriptDef.trimCurrentDirectory( currentDirectory ) ;
        String fileName = new StringBuilder().append( currentDirectory ).
            append( MaachangDef.DIRECTORY_APPLICATION ).
            append( ( ( path.startsWith("/" ) ) ? path.substring( 1,path.length() ) : path ) ).
            toString() ;
        if( FileUtil.isFileExists( fileName ) == false ) {
            return null ;
        }
        String script = FileUtil.getFileByString( fileName,"UTF8" ) ;
        if( script == null ) {
            return null ;
        }
        String[] sc = new String[]{script} ;
        long tm = PageCache.getInstance().analysisCacheMode( sc ) ;
        if( tm > 0L ) {
            script = sc[ 0 ] ;
        }
        sc = null ;
        return script ;
    }
}
