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

import java.util.ArrayList;

import javax.script.ScriptException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.comet.MaachangDef;
import org.maachang.comet.ServiceDef;
import org.maachang.comet.httpd.engine.script.cache.CacheScriptManager;
import org.maachang.comet.httpd.engine.script.scripts.ApplicationScriptFactory;
import org.maachang.manager.GlobalManager;
import org.maachang.util.StringUtil;

/**
 * スクリプト実行例外.
 * 
 * @version 2007/09/23
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class BaseScriptException extends Exception {
    
    private static final long serialVersionUID = -8407798868124851487L;
    
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( BaseScriptException.class ) ;
    
    /**
     * 行を指す開始情報.
     */
    private static final String START_LINE = "(" ;
    
    /**
     * 行を指す終了情報.
     */
    private static final String END_LINE = ")" ;
    
    /**
     * 出力行数.
     */
    private static final int OUTPUT_LINE_SIZE = MaachangDef.OUTPUT_ERROR_LIST ;
    
    /**
     * ScriptException.
     */
    private ScriptException scriptException = null ;
    
    /**
     * スクリプトエラー情報.
     */
    private String errorScriptInfo = null ;
    
    /**
     * カレントディレクトリ名.
     */
    private static final String CURRENT_DIR ;
    
    static {
        String s = null ;
        try {
            s = ScriptDef.trimCurrentDirectory( "." ) ;
        } catch( Exception e ) {
            s = null ;
        }
        CURRENT_DIR = s ;
    }
    
    /**
     * 関数予約語群.
     */
    private static final String[] REM_NAMES = {
        "_eval",
        "_exit",
        "boolean",
        "break",
        "byte",
        "case",
        "catch",
        "char",
        "default",
        "delete",
        "do",
        "double",
        "else",
        "eval",
        "exit",
        "false",
        "float",
        "for",
        "function",
        "if",
        "instanceof",
        "int",
        "long",
        "new",
        "null",
        "return",
        "short",
        "switch",
        "this",
        "throw",
        "true",
        "try",
        "typeof",
        "var",
        "void",
        "while",
        // maachangComet独自予約メソッド.
        "isNull",
        "useString",
        "startsWith",
        "endsWith",
        "trim",
        "print",
        "println",
        "reload",
        "include",
        "httpPrint",
        "httpPrintln",
        "valueof",
        "bindings",
        "jmap",
        "jlist",
        "isNumeric",
        "isDouble"
    } ;
    
    /**
     * 行番号カラー.
     */
    private static final String LINE_COLOR = "#7f7f7f" ;
    
    /**
     * コーテーションカラー.
     */
    private static final String COTE_COLOR = "#ff00ff" ;
    
    /**
     * コメントカラー.
     */
    private static final String COMMENT_COLOR = "#00ff00" ;
    
    /**
     * 予約語カラー.
     */
    private static final String REM_COLOR = "#00ffff" ;
    
    /**
     * 枠カラー.
     */
    private static final String WAKU_COLOR = "#0000ffff" ;
    
    /**
     * エラー原因出力カラー.
     */
    private static final String DV_ERR_COLOR = "#0011cc" ;
    
    /**
     * 行番号桁.
     */
    private static final String LINE_VIEW = "00000" ;
    
    /**
     * ライン情報.
     */
    private static final String LINE_INFO = "<table width=100% cellpadding=0 cellspacing=0><tr><td bgcolor="+WAKU_COLOR+">"+
        "<img alt=\"\" width=1 height=2></td></tr></table>" ;
    
    /**
     * スクロール終了タグ.
     */
    private static final String END_SCROLL = "</div>" ;
    
    /**
     * コンストラクタ.
     */
    private BaseScriptException() {
        
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * 情報を設定してオブジェクトを生成します.
     * <BR>
     * @param scriptException スクリプト例外を設定します.
     * @param script 対象のスクリプト情報を設定します.
     */
    public BaseScriptException( ScriptException scriptException ) {
        if( scriptException == null ) {
            return ;
        }
        this.scriptException = scriptException ;
        this.errorScriptInfo = convertErrorInfo( scriptException,scriptException.getMessage() ) ;
    }
    
    /**
     * スクリプト例外を取得.
     * <BR><BR>
     * スクリプト例外を取得します.
     * <BR>
     * @return ScriptException スクリプト例外が返されます.
     */
    public ScriptException getScriptException() {
        return scriptException ;
    }
    
    /**
     * スクリプトエラー情報を取得.
     * <BR><BR>
     * スクリプトエラー情報を取得します.
     * <BR>
     * @return String スクリプトエラー情報が返されます.
     */
    public String getErrorScriptInfo() {
        return errorScriptInfo ;
    }
    
    /**
     * スクリプトエラー行数を取得.
     */
    private String convertErrorInfo( Exception exception,String msg ) {
        String name = null ;
        SrcScript src = null ;
        ArrayList<ErrorScriptLine> list = getErrorLine( msg ) ;
        if( list == null || list.size() <= 0 ) {
            return "" ;
        }
        int len = list.size() ;
        StringBuilder buf = new StringBuilder() ;
        for( int x = 0 ; x < len ; x ++ ) {
            if( x != 0 ) {
                buf.append( "<BR>" ) ;
            }
            String directory = "" ;
            ErrorScriptLine err = list.get( x ) ;
            String scriptName = err.getName() ;
            int line = err.getLine() ;
            err = null ;
            if( LOG.isDebugEnabled() ) {
                LOG.debug( "... errorScript:" + scriptName ) ;
            }
            ApplicationScriptFactory factory = getFactory() ;
            
            // アプリケーションスクリプト.
            try {
                Script script = factory.getApplication( scriptName ) ;
                if( script != null ) {
                    src = script.getSrcScript() ;
                    name = script.getScriptName() ;
                    if( CURRENT_DIR != null ) {
                        name = name.substring( CURRENT_DIR.length() ) ;
                    }
                }
                else {
                    src = null ;
                    name = null ;
                }
            } catch( Exception e ) {
                src = null ;
                name = null ;
            }
            if( src == null ) {
                // キャッシュデフォルトスクリプト.
                if( CacheScriptManager.getInstance().isCache( scriptName ) == true ) {
                    src = CacheScriptManager.getInstance().getSrcScript( scriptName ) ;
                    name = scriptName ;
                    directory = CacheScriptManager.getInstance().getDirectory( scriptName ) ;
                }
            }
            if( src == null ) {
                continue ;
            }
            // SrcScriptListを取得.
            SrcScriptList srcLst = src.getLineBySrcScriptList( line ) ;
            // SrcScriptListが存在しない場合.
            if( srcLst == null ) {
                buf.append( "<code>src&nbsp;&nbsp;:</code>&nbsp;" ) ;
                buf.append( name ).append( "&nbsp;&nbsp;行：" ).append( line ) ;
                buf.append( "<BR>" ) ;
                buf.append( LINE_INFO ) ;
                buf.append( "<div id=\"err_scroll_" ).append( x ).append( "\" class=\"err_scroll\">" ) ;
                int start = line - ( OUTPUT_LINE_SIZE / 2 ) ;
                int state = 0 ;
                int cnt = 0 ;
                int movePos = 0 ;
                for( int i = 0 ; i < OUTPUT_LINE_SIZE ; i ++ ) {
                    int pos = i+start ;
                    if( pos <= 0 || pos > src.lines() ) {
                        continue ;
                    }
                    String sc = src.get( pos ) ;
                    if( sc == null ) {
                        buf.append( "<span style=\"color: " ).append( LINE_COLOR ).append( ";\">" ) ;
                        buf.append( LINE_VIEW.substring( String.valueOf( pos ).length() ) ) ;
                        buf.append( pos ) ;
                        buf.append( "&nbsp;：&nbsp;" ).append( "</span>" ) ;
                        buf.append( "<BR>" ) ;
                        cnt ++ ;
                        continue ;
                    }
                    if( sc.length() > 0 ) {
                        sc = StringUtil.changeString( sc," ","&nbsp;" ) ;
                    }
                    if( pos == line ) {
                        movePos = cnt ;
                        buf.append( "<span class=\"err_line\"><font color=#cc0000>" ) ;
                    }
                    buf.append( "<span style=\"color: " ).append( LINE_COLOR ).append( ";\">" ) ;
                    buf.append( LINE_VIEW.substring( String.valueOf( pos ).length() ) ) ;
                    buf.append( pos ) ;
                    buf.append( "&nbsp;：&nbsp;" ).append( "</span>" ) ;
                    if( pos == line ) {
                        buf.append( sc ) ;
                        buf.append( "</font>" ) ;
                        buf.append( "<font color=" ).append( DV_ERR_COLOR ).append( ">" ).
                            append( "・・・・" ).append( trimErrorMessage( scriptName,exception.getMessage() ) ) ;
                        buf.append( "</font></font></span>" ) ;
                    }
                    else {
                        StringBuilder cb = new StringBuilder() ;
                        state = putColor( cb,state,sc ) ;
                        sc = cb.toString() ;
                        cb = null ;
                        buf.append( remColor( sc ) ) ;
                    }
                    buf.append( "<BR>" ) ;
                    cnt ++ ;
                }
                if( state != 0 ) {
                    buf.append( "</span>" ) ;
                }
                buf.append( END_SCROLL ) ;
                buf.append( LINE_INFO ) ;
                
                // スクロールを行うJavascriptを付加.
                buf.append( scrollJavascript( movePos,x ) ) ;
            }
            // SrcScriptListが存在する場合.
            else {
                buf.append( "<code>src&nbsp;&nbsp;:</code>&nbsp;" ) ;
                buf.append( directory ).append( srcLst.getName() ).append( "&nbsp;&nbsp;行：" ).
                    append( ( line - srcLst.getStart() ) ) ;
                buf.append( "<BR>" ) ;
                buf.append( LINE_INFO ) ;
                buf.append( "<div id=\"err_scroll_" ).append( x ).append( "\" class=\"err_scroll\">" ) ;
                int start = line - ( OUTPUT_LINE_SIZE / 2 ) ;
                int state = 0 ;
                int cnt = 0 ;
                int movePos = 0 ;
                for( int i = 0 ; i < OUTPUT_LINE_SIZE ; i ++ ) {
                    int pos = i+start ;
                    int viewLine = pos - srcLst.getStart() ;
                    if( viewLine <= 0 || viewLine > srcLst.getLength() ) {
                        continue ;
                    }
                    String sc = src.get( pos ) ;
                    if( sc == null ) {
                        buf.append( "<span style=\"color: " ).append( LINE_COLOR ).append( ";\">" ) ;
                        buf.append( LINE_VIEW.substring( String.valueOf( viewLine ).length() ) ) ;
                        buf.append( viewLine ) ;
                        buf.append( "&nbsp;：&nbsp;" ).append( "</span>" ) ;
                        buf.append( "<BR>" ) ;
                        cnt ++ ;
                        continue ;
                    }
                    if( sc.length() > 0 ) {
                        sc = StringUtil.changeString( sc," ","&nbsp;" ) ;
                    }
                    if( pos == line ) {
                        movePos = cnt ;
                        buf.append( "<span class=\"err_line\"><font color=#cc0000>" ) ;
                    }
                    
                    buf.append( "<span style=\"color: " ).append( LINE_COLOR ).append( ";\">" ) ;
                    buf.append( LINE_VIEW.substring( String.valueOf( viewLine ).length() ) ) ;
                    buf.append( viewLine ) ;
                    buf.append( "&nbsp;：&nbsp;" ).append( "</span>" ) ;
                    if( pos == line ) {
                        buf.append( sc ) ;
                        buf.append( "</font>" ) ;
                        buf.append( "<font color=" ).append( DV_ERR_COLOR ).append( ">" ).
                            append( "・・・・" ).append( trimErrorMessage( scriptName,exception.getMessage() ) ) ;
                        buf.append( "</font></font></span>" ) ;
                    }
                    else {
                        StringBuilder cb = new StringBuilder() ;
                        state = putColor( cb,state,sc ) ;
                        sc = cb.toString() ;
                        cb = null ;
                        buf.append( remColor( sc ) ) ;
                    }
                    buf.append( "<BR>" ) ;
                    cnt ++ ;
                }
                if( state != 0 ) {
                    buf.append( "</span>" ) ;
                }
                buf.append( END_SCROLL ) ;
                buf.append( LINE_INFO ) ;
                
                // スクロールを行うJavascriptを付加.
                buf.append( scrollJavascript( movePos,x ) ) ;
            }
        }
        return buf.toString() ;
    }
    
    /**
     * エラー行数を取得.
     */
    private ArrayList<ErrorScriptLine> getErrorLine( String msg ) {
        int p = msg.indexOf( START_LINE ) ;
        if( p == -1 ) {
            return null ;
        }
        ArrayList<ErrorScriptLine> ret = new ArrayList<ErrorScriptLine>() ;
        for( ;; ) {
            if( isErrorInfo( msg,p ) == true ) {
                int p1 = msg.indexOf( "#",p ) ;
                int e = msg.indexOf( END_LINE,p ) ;
                if( p1 == -1 || e == -1 ) {
                    break ;
                }
                String str = msg.substring( p1+"#".length(),e ) ;
                int line = -1 ;
                try {
                    line = Integer.parseInt( str ) ;
                } catch( Exception ee ) {
                    line = -1 ;
                }
                if( line == -1 ) {
                    p = msg.indexOf( START_LINE,p+1 ) ;
                    if( p == -1 ) {
                        break ;
                    }
                    continue ;
                }
                String script = msg.substring( p+START_LINE.length(),p1 ) ;
                if( script != null ) {
                    script = script.trim() ;
                    ErrorScriptLine ch = new ErrorScriptLine() ;
                    ch.setLine( line ) ;
                    ch.setName( script ) ;
                    ret.add( ch ) ;
                }
            }
            p = msg.indexOf( START_LINE,p+1 ) ;
            if( p == -1 ) {
                break ;
            }
        }
        return ret ;
    }
    
    /**
     * 指定位置のエラー情報が正しいかチェック.
     */
    private static final boolean isErrorInfo( String msg,int pos ) {
        int p = msg.indexOf( END_LINE,pos ) ;
        if( p == -1 ) {
            return false ;
        }
        int b = msg.lastIndexOf( "#",p ) ;
        if( b == -1 || b > p ) {
            return false ;
        }
        return true ;
    }
    
    /**
     * WebAppScriptFactoryを取得.
     */
    private static final ApplicationScriptFactory getFactory() {
        return ( ApplicationScriptFactory )GlobalManager.getValue(
            ServiceDef.MANAGER_BY_WEB_APP_FACTORY ) ;
    }
    
    /**
     * 対象スクリプトにカラーを付加する.
     */
    private static final int putColor( StringBuilder buf,int state,String line ) {
        int len = line.length() ;
        int c = -1 ;
        int back = -1 ;
        int cote = -1 ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( c != -1 ) {
                back = c ;
            }
            c = line.charAt( i ) ;
            if( state == 1 ) {
                if( c == '*' && i+1 < line.length() ) {
                    if( c == '/' ) {
                        buf.append( "*/</span>" ) ;
                        state = 0 ;
                        continue ;
                    }
                }
            }
            else {
                if( cote != -1 ) {
                    if( cote == c && back != '\\' ) {
                        cote = -1 ;
                        buf.append( ( char )c ).append( "</span>" ) ;
                        continue ;
                    }
                }
                else {
                    if( ( c == '\'' || c == '\"' ) && back != '\\'  ) {
                        cote = c ;
                        buf.append( "<span style=\"color: " ).append( COTE_COLOR ).append( ";\">" ).append( ( char )c ) ;
                        continue ;
                    }
                    else if( c == '/' && i+1 < line.length() ) {
                        char next = line.charAt( i+1 ) ;
                        if( next == '/' ) {
                            buf.append( "<span style=\"color: " ).append( COMMENT_COLOR ).append( "\">" ).
                                append( line.substring( i ) ).append( "</span>" ) ;
                            break ;
                        }
                        else if( next == '*' ) {
                            state = 1 ;
                            i += 1 ;
                            buf.append( "<span style=\"color: " ).append( COMMENT_COLOR ).append( ";\">" ).append( "/*" ) ;
                            continue ;
                        }
                    }
                }
            }
            buf.append( ( char )c ) ;
        }
        if( cote != -1 ) {
            buf.append( "</span>" ) ;
        }
        return state ;
    }
    
    /**
     * 予約語のカラーを付加する.
     */
    private static final String remColor( String line ) {
        String base = line ;
        try {
            int len = REM_NAMES.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                String x = REM_NAMES[ i ] ;
                int b = 0 ;
                for( ;; ) {
                    int p = StringUtil.indexToNotCote( line,x,b ) ;
                    //int p = line.indexOf( x,b ) ;
                    if( p <= -1 ) {
                        break ;
                    }
                    else {
                        boolean chk = true ;
                        if( p-1 >= 0 ) {
                            char s = line.charAt( p-1 ) ;
                            if( ( s >= 'a' && s <= 'z' ) ||
                                ( s >= 'A' && s <= 'Z' ) ||
                                ( s >= '0' && s <= '9' ) ||
                                s == '_' ) {
                                chk = false ;
                            }
                        }
                        if( chk == true && p+x.length() < line.length() ) {
                            char s = line.charAt( p+x.length() ) ;
                            if( ( s >= 'a' && s <= 'z' ) ||
                                ( s >= 'A' && s <= 'Z' ) ||
                                ( s >= '0' && s <= '9' ) ||
                                s == '_' ) {
                                chk = false ;
                            }
                        }
                        if( chk ) {
                            String ss = new StringBuilder().append( line.substring( 0,p ) ).
                                append( "<span style=\"color: " ).append( REM_COLOR ).append( "\">" ).
                                append( x ).append( "</span>" ).
                                append( line.substring( p+x.length() ) ).toString() ;
                            b = p+x.length() + ( ss.length() - line.length() ) ;
                            line = ss ;
                            ss = null ;
                        }
                        else {
                            b = p+x.length() ;
                        }
                    }
                }
            }
        } catch( Exception e ) {
            line = base ;
        }
        return line ;
    }
    
    /**
     * エラーメッセージを整形.
     */
    private static final String trimErrorMessage( String scriptName,String message ) {
        int p = message.lastIndexOf( new StringBuilder().append( "(" ).append( scriptName ).append( "#" ).toString() ) ;
        if( p <= -1 ) {
            return message ;
        }
        int sLen = "Exception: ".length() ;
        int s = message.lastIndexOf( "Exception: ",p ) ;
        int s2 = message.lastIndexOf( ") in ",p ) ;
        if( s <= -1 ) {
            sLen = "Error: ".length() ;
            s = message.lastIndexOf( "Error: ",p ) ;
        }
        if( s <= -1 && s2 <= -1 ) {
            return message ;
        }
        if( s <= -1 || ( s2 > -1 && s2 > s ) ) {
            s = s2 + ") in ".length() ;
        }
        else {
            s = s + sLen ;
        }
        return message.substring( s,p ) ;
    }
    
    /**
     * 移動加速度.
     */
    private static final double ONE_MOVE = 13.45f ;
    
    /**
     * スクロールを行うJavascriptを作成.
     */
    private static final String scrollJavascript( int movePos,int idNum ) {
        return new StringBuilder().append( "\n\n<script>" ).
            append( "(function() {" ).
            append( "var t = document.getElementById( \"err_scroll_" ).append( idNum ).append( "\" ) ;" ).
            append( "t.scrollTop=" ).append( (int)((double)movePos*ONE_MOVE) ).append( " ;" ).
            append( "})() ;" ).append( "</script>\n" ).toString() ;
    }
}
