package org.maachang.comet;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.comet.conf.MinStartupConfig;
import org.maachang.comet.httpd.HttpdVersionDef;
import org.maachang.comet.httpd.engine.script.BaseModel;
import org.maachang.comet.httpd.engine.script.BaseModelImpl;
import org.maachang.comet.httpd.engine.script.EndScript;
import org.maachang.comet.httpd.engine.script.ScriptDef;
import org.maachang.comet.httpd.engine.script.cache.CacheScriptManager;
import org.maachang.comet.httpd.engine.script.dao.DaoFactory;
import org.maachang.comet.httpd.engine.script.js.InitJS;
import org.maachang.comet.httpd.engine.script.scripts.ApplicationScriptFactory;
import org.maachang.jsr.script.api.ApiManager;
import org.maachang.jsr.script.javascript.RhinoScriptEngine;
import org.maachang.manager.GlobalManager;
import org.maachang.util.FileUtil;
import org.maachang.util.StringUtil;

/**
 * MaachangCometコンソールスクリプト.
 * 
 * @version 2007/08/26
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class ConsoleScript {
    
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( ConsoleScript.class ) ;
    
    /**
     * コンストラクタ.
     */
    private ConsoleScript() {
        
    }
    
    /**
     * メインメソッド.
     * <BR><BR>
     * @param args Javaからのパラメータが設定されます.
     * @exception Exception 例外.
     */
    public static final void main( String[] args )
        throws Exception {
        new ConsoleScript().execution( args ) ;
    }
    
    /**
     * 内部変数.
     */
    private ScriptContext ctx = null ;
    private ApplicationScriptFactory webApp = null ;
    private BaseModel baseModel = null ;
    private String path = null ;
    private ScriptEngine consoleEngine = null ;
    
    /**
     * 実行処理.
     */
    private void execution( String[] args )
        throws Exception {
        this.path = FileUtil.getFullPath( "." )+FileUtil.FILE_SPACE ;
        
        ScriptEngineFactory fac = null ;
        if( ApiManager.getInstance() != null ) {
            ScriptEngine eng = ApiManager.getInstance().getScriptEngine() ;
            if( eng != null ) {
                fac = eng.getFactory() ;
            }
            eng = null ;
        }
        if( args != null && args.length > 0 ) {
            LOG.info( "## maachang comet - console .. " ) ;
            LOG.info( "## [ver."+HttpdVersionDef.getVersion()+" update."+HttpdVersionDef.getDate()+"]" ) ;
            LOG.info( "##" ) ;
            if( fac == null ) {
                LOG.error( "## no-javascript suport!!" ) ;
                return ;
            }
            LOG.info( "## engineName:" + fac.getEngineName() ) ;
            LOG.info( "## engineVersion:" + fac.getEngineVersion() ) ;
            LOG.info( "##" ) ;
        }
        else {
            System.out.println( "maachang comet - console .. " ) ;
            System.out.println( "[ver."+HttpdVersionDef.getVersion()+" update."+HttpdVersionDef.getDate()+"]" ) ;
            System.out.println() ;
            if( fac == null ) {
                System.err.println( "no-javascript suport!!" ) ;
                return ;
            }
            System.out.println( "engineName:" + fac.getEngineName() ) ;
            System.out.println( "engineVersion:" + fac.getEngineVersion() ) ;
            System.out.println() ;
        }
        fac = null ;
        
        // 最小コンフィグ読み込み.
        MinStartupConfig.init( path ) ;
        
        // スクリプトFactory情報を読み込み.
        this.webApp = new ApplicationScriptFactory( path ) ;
        GlobalManager.getInstance().put( ServiceDef.MANAGER_BY_WEB_APP_FACTORY,webApp ) ;
        
        // DaoFactoryを初期化.
        DaoFactory daoFactory = new DaoFactory() ;
        GlobalManager.getInstance().put( ServiceDef.DAO_FACTORY,daoFactory ) ;
        
        // スクリプトを読み込む.
        readScript() ;
        
        // コンソール用スクリプトエンジンを生成.
        createConsoleEngine() ;
        
        // バッチモードで起動する場合.
        if( args != null && args.length > 0 ) {
            StringBuilder buf = new StringBuilder() ;
            StringBuilder names = new StringBuilder() ;
            batch( buf ) ;
            buf.append( InitJS.getInstance().get() ) ;
            int len = args.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( i != 0 ) {
                    names.append( "," ) ;
                }
                names.append( args[ i ] ) ;
                String str = FileUtil.getFileByString( args[ i ] ) ;
                if( str == null ) {
                    throw new IOException( "指定ファイル名[" + args[ i ] + "]は存在しません" ) ;
                }
                buf.append( str ) ;
                LOG.info( "## readBatch:" + args[ i ] ) ;
            }
            String nms = names.toString() ;
            names = null ;
            String script = buf.toString() ;
            buf = null ;
            // 処理実行.
            try {
                //script = ConvertScript.convert( script ) ;
                //consoleEngine.put( ScriptEngine.FILENAME,"console["+nms+"]" ) ;
                ApiManager.setScriptName( "console["+nms+"]" ) ;
                consoleEngine.eval( script,ctx ) ;
            } catch( Exception e ) {
                LOG.error( "## error",e ) ;
            }
        }
        // コンソール実行.
        else {
            startConsole() ;
        }
    }
    
    /**
     * バッチモードでのコンソール表示用内容をログ出力用に変換.
     */
    private void batch( StringBuilder buf ) {
        buf.append( "var print = function(info) { org.maachang.comet.ConsoleScript.logout(info); };\n" ).
            append( "var println = function(info) { org.maachang.comet.ConsoleScript.logout(info); };\n" ).
            append( "var p = function(info) { org.maachang.comet.ConsoleScript.logout(info); };\n" ).
            append( "var puts = function(info) { org.maachang.comet.ConsoleScript.logout(info); };\n" ).
            append( "var alert = function(info) { org.maachang.comet.ConsoleScript.logout(info); };\n" ) ;
    }
    
    /**
     * ログ出力用.
     */
    public static final void logout( String string ) {
        if( string != null && string.length() <= 0 ) {
            return ;
        }
        LOG.info( string ) ;
    }
    
    /**
     * スクリプトを読み込む.
     */
    private void readScript()
        throws Exception {
        this.ctx = new SimpleScriptContext() ;
        this.baseModel = new BaseModelImpl() ;
        
        System.out.println( "" ) ;
        
        // ローカルデータを破棄.
        CacheScriptManager.getInstance().exitScript() ;
        
        // キャッシュリロード処理.
        CacheScriptManager.getInstance().reload() ;
        
        // キャッシュスクリプトマネージャを読み込む.
        CacheScriptManager.getInstance().script( this.ctx ) ;
        
        // デフォルトパラメータを読み込む.
        readDefaultParams() ;
        
        // デフォルト条件を設定.
        ScriptDef.setDirectoryByBindings( this.ctx ) ;
        
    }
    
    /**
     * デフォルトパラメータを読み込む.
     */
    private void readDefaultParams()
        throws Exception {
        Bindings bindings = ctx.getBindings( ScriptContext.ENGINE_SCOPE ) ;
        bindings.put( ScriptDef.CURRENT_SCRIPT,ScriptDef.MODE_CONSOLE ) ;
        bindings.put( ScriptDef.SCRIPT_MODE,ScriptDef.MODE_CONSOLE ) ;
        bindings.put( ScriptDef.SCRIPT_DETAIL,ScriptDef.MODE_CONSOLE ) ;
        bindings.put( ScriptDef.SCRIPT_BY_MODEL,baseModel ) ;
        bindings.put( ScriptDef.SCRIPT_BY_PATH,path ) ;
        bindings.put( ScriptDef.SCRIPT_BY_CTX,ctx ) ;
        bindings.put( ScriptDef.SCRIPT_BY_LOG,LOG ) ;
    }
    
    /**
     * コンソール実行用スクリプトエンジンを生成.
     */
    private void createConsoleEngine()
        throws Exception {
        this.consoleEngine = ApiManager.getInstance().getScriptEngine() ;
        Bindings bindings = ctx.getBindings( ScriptContext.ENGINE_SCOPE ) ;
        bindings.put( ScriptDef.SCRIPT_BY_ENGINE,this.consoleEngine ) ;
        consoleEngine.eval( InitJS.getInstance().get(),ctx ) ;
    }
    
    /**
     * 基本コマンドライン.
     */
    private static final String RELOAD = "reload" ;
    private static final String EXIT = "exit" ;
    private static final String MEM = "mem" ;
    private static final String GC = "gc" ;
    private static final String LIST = "list" ;
    private static final String VIEW = "view" ;
    private static final String CLS = "cls" ;
    
    /**
     * コンソール実行.
     */
    private void startConsole()
        throws Exception {
        StringBuilder buf = null ;
        System.out.println() ;
        BufferedReader bo = new BufferedReader(
            new InputStreamReader( System.in ) ) ;
        for( ;; ) {
            System.out.print( "$>" ) ;
            String one = bo.readLine() ;
            if( one == null || ( one = one.trim() ).length() <= 0 ) {
                continue ;
            }
            one = one.trim() ;
            String ck = one.toLowerCase() ;
            if( EXIT.equals( ck ) ) {
                if( baseModel.isCreate() == true ) {
                    baseModel.commit() ;
                }
                System.out.println( " ... success exit" ) ;
                return ;
            }
            if( ck.startsWith( "!" ) ) {
                ck = ck.substring( "!".length(),ck.length() ) ;
                // リロード.
                if( RELOAD.equals( ck ) ) {
                    readScript() ;
                    createConsoleEngine() ;
                    System.out.println( " ... success reload" ) ;
                    continue ;
                }
                // 処理終了.
                else if( EXIT.equals( ck ) ) {
                    if( baseModel.isCreate() == true ) {
                        baseModel.commit() ;
                    }
                    System.out.println( " ... success exit" ) ;
                    return ;
                }
                // メモリー参照.
                else if( MEM.equals( ck ) ) {
                    Bindings bindings = ctx.getBindings( ScriptContext.ENGINE_SCOPE ) ;
                    System.out.println( "+-------------------------------------------" ) ;
                    String[] objs = CacheScriptManager.getInstance().getNames( bindings ) ;
                    for( int i = 0 ; i < objs.length ; i ++ ) {
                        System.out.println( objs[ i ] + " : " + bindings.get( objs[ i ] ) ) ;
                    }
                    System.out.println( "+-------------------------------------------" ) ;
                    System.out.println( "total:" + Runtime.getRuntime().totalMemory() ) ;
                    System.out.println( "max  :" + Runtime.getRuntime().maxMemory() ) ;
                    System.out.println( "free :" + Runtime.getRuntime().freeMemory() ) ;
                    continue ;
                }
                // 利用可能メソッド出力.
                else if( LIST.equals( ck ) ) {
                    Bindings bindings = ctx.getBindings( ScriptContext.ENGINE_SCOPE ) ;
                    String[] objs = CacheScriptManager.getInstance().getNames( bindings ) ;
                    if( objs != null ) {
                        ArrayList<String> lst = new ArrayList<String>() ;
                        for( int i = 0 ; i < objs.length ; i ++ ) {
                            Object o = bindings.get( objs[ i ] ) ;
                            if( o == null ) {
                                continue ;
                            }
                            String st = o.getClass().getName() ;
                            if( bindings.get( objs[ i ] ) != null &&
                                ( st.indexOf( "org.mozilla.javascript.gen" ) != -1 ||
                                st.indexOf( "org.mozilla.javascript.InterpretedFunction" ) != -1 ||
                                st.indexOf( "org.mozilla.javascript.FunctionObject" ) != -1 ) ) {
                                lst.add( ( String )objs[ i ] ) ;
                            }
                        }
                        String[] strs = sort( lst ) ;
                        lst = null ;
                        outputView( strs ) ;
                    }
                    continue ;
                }
                // 利用可能View情報出力.
                else if( VIEW.equals( ck ) ) {
                    Bindings bindings = ctx.getBindings( ScriptContext.ENGINE_SCOPE ) ;
                    String[] objs = CacheScriptManager.getInstance().getNames( bindings ) ;
                    if( objs != null ) {
                        ArrayList<String> lst = new ArrayList<String>() ;
                        for( int i = 0 ; i < objs.length ; i ++ ) {
                            Object o = bindings.get( objs[ i ] ) ;
                            if( o == null ) {
                                continue ;
                            }
                            String st = o.getClass().getName() ;
                            if( st.indexOf( "org.mozilla.javascript.gen" ) == -1 &&
                                st.indexOf( "org.mozilla.javascript.InterpretedFunction" ) == -1 &&
                                st.indexOf( "org.mozilla.javascript.FunctionObject" ) == -1 &&
                                ( ( String )objs[ i ] ).indexOf( "_" ) == -1 &&
                                ( ( String )objs[ i ] ).indexOf( "$" ) == -1 ) {
                                lst.add( ( String )objs[ i ] ) ;
                            }
                        }
                        String[] strs = sort( lst ) ;
                        lst = null ;
                        outputView( strs ) ;
                    }
                    continue ;
                }
                // GC起動.
                else if( GC.equals( ck ) ) {
                    Runtime.getRuntime().gc() ;
                    continue ;
                }
                // 画面クリア.
                else if( CLS.equals( ck ) ) {
                    for( int i = 0 ; i < 120 ; i ++ ) {
                        System.out.println() ;
                    }
                    continue ;
                }
                // help内容を表示.
                else if( one.length() == 1 ) {
                    viewHelp() ;
                    continue ;
                }
            }
            else if( ck.startsWith( "?" ) ) {
                viewHelp() ;
                continue ;
            }
            // スクリプト内容を実行.
            //one = ConvertScript.convert( one ) ;
            if( one.endsWith( "\\" ) ) {
                if( buf == null ) {
                    buf = new StringBuilder() ;
                }
                buf.append( one.substring( 0,one.length()-1 ) ).append( "\n" ) ;
                continue ;
            }
            else {
                if( buf != null ) {
                    buf.append( one ).append( "\n" ) ;
                    one = buf.toString() ;
                }
                buf = null ;
            }
            try {
                //consoleEngine.put( ScriptEngine.FILENAME,"console" ) ;
                ApiManager.setScriptName( "console" ) ;
                Object obj = consoleEngine.eval( one,ctx ) ;
                if( obj != null ) {
                    if( className( obj ).startsWith( "Native" ) ) {
                        System.out.println( "[NativeObject]" ) ;
                    }
                    else {
                        System.out.println( RhinoScriptEngine._convertString( obj ) ) ;
                    }
                }
                else {
                    System.out.println() ;
                }
            } catch( ScriptException sc ) {
                if( EndScript.isEndScript( sc ) == true ) {
                    if( baseModel.isCreate() == true ) {
                        baseModel.commit() ;
                    }
                    System.out.println( "... exit" ) ;
                    return ;
                }
                System.out.println( sc.getMessage() ) ;
            } catch( Exception e ) {
                System.out.println( e ) ;
            }
            
        }
    }
    
    private static final String className( Object o ) {
        if( o == null ) {
            return "Null" ;
        }
        String s = o.getClass().getName() ;
        int p = s.lastIndexOf( "." ) ;
        if( p != -1 ) {
            return s.substring( p+1 ) ;
        }
        return s ;
    }
    
    /**
     * 対象情報をソート.
     */
    private static final String[] sort( ArrayList<String> lst ) {
        String[] strs = null ;
        int len = lst.size() ;
        if( len > 0 ) {
            strs = new String[ len ] ;
            for( int i = 0 ; i < len ; i ++ ) {
                strs[ i ] = lst.get( i ) ;
            }
            Arrays.sort( strs ) ;
        }
        return strs ;
    }
    
    /**
     * 内容表示.
     */
    private static final void outputView( String[] strs ) {
        int cnt = 0 ;
        if( strs != null ) {
            for( int i = 0 ; i < strs.length ; i ++ ) {
                String view = strs[ i ] ;
                int viewLen = view.length() ;
                if( viewLen <= 16 ) {
                    view = "["+view+"] "+StringUtil.createString( ' ',16-viewLen ) ;
                }
                else {
                    view = "["+view+"] "+StringUtil.createString( ' ',(19*2)-(viewLen+3) ) ;
                }
                System.out.print( view ) ;
                cnt += view.length() ;
                if( cnt >= 75 ) {
                    System.out.println() ;
                    cnt = 0 ;
                }
            }
            System.out.println() ;
        }
        System.out.println() ;
    }
    
    private static final void viewHelp() {
        System.out.println( "** help ************************************" ) ;
        System.out.println( "!reload : ライブラリを再読み込み." ) ;
        System.out.println( "!exit   : スクリプトを終了." ) ;
        System.out.println( "!mem    : メモリ情報を出力." ) ;
        System.out.println( "!gc     : ガページコレクターを実行." ) ;
        System.out.println( "!list   : 有効メソッド一覧." ) ;
        System.out.println( "!view   : 有効変数一覧." ) ;
        System.out.println( "!cls    : 画面クリア." ) ;
        System.out.println( "** help ************************************" ) ;
        System.out.println() ;
    }
}
