/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Rhino JavaScript Debugger code, released
 * November 21, 2000.
 *
 * The Initial Developer of the Original Code is
 * SeeBeyond Corporation.
 * Portions created by the Initial Developer are Copyright (C) 2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Igor Bukanov
 *   Matt Gould
 *   Christopher Oliver
 *   Cameron McCormack
 *
 * Alternatively, the contents of this file may be used under the terms of
 * the GNU General Public License Version 2 or later (the "GPL"), in which
 * case the provisions of the GPL are applicable instead of those above. If
 * you wish to allow use of your version of this file only under the terms of
 * the GPL and not to allow others to use your version of this file under the
 * MPL, indicate your decision by deleting the provisions above and replacing
 * them with the notice and other provisions required by the GPL. If you do
 * not delete the provisions above, a recipient may use your version of this
 * file under either the MPL or the GPL.
 *
 * ***** END LICENSE BLOCK ***** */

package org.mozilla.javascript.tools.debugger;

import java.io.InputStream;
import java.io.PrintStream;

import javax.swing.JFrame;

import org.mozilla.javascript.*;
import org.mozilla.javascript.tools.shell.Global;

/**
 * Rhino script debugger main class.  This class links together a
 * debugger object ({@link Dim}) and a debugger GUI object ({@link SwingGui}).
 */
public class Main {

    /**
     * The debugger.
     */
    private Dim dim;

    /**
     * The debugger frame.
     */
    private SwingGui debugGui;

    /**
     * Creates a new Main.
     */
    public Main(String title) {
        dim = new Dim();
        debugGui = new SwingGui(dim, title);
    }

    /**
     * Returns the debugger window {@link JFrame}.
     */
    public JFrame getDebugFrame() {
        return debugGui;
    }

    /**
     * Breaks execution of the script.
     */
    public void doBreak() {
        dim.setBreak();
    }

    /**
     * Sets whether execution should break when a script exception is thrown.
     */
    public void setBreakOnExceptions(boolean value) {
        dim.setBreakOnExceptions(value);
        debugGui.getMenubar().getBreakOnExceptions().setSelected(value);
    }

    /**
     * Sets whether execution should break when a function is entered.
     */
    public void setBreakOnEnter(boolean value) {
        dim.setBreakOnEnter(value);
        debugGui.getMenubar().getBreakOnEnter().setSelected(value);
    }

    /**
     * Sets whether execution should break when a function is left.
     */
    public void setBreakOnReturn(boolean value) {
        dim.setBreakOnReturn(value);
        debugGui.getMenubar().getBreakOnReturn().setSelected(value);
    }

    /**
     * Removes all breakpoints.
     */
    public void clearAllBreakpoints() {
        dim.clearAllBreakpoints();
    }

    /**
     * Resumes execution of the script.
     */
    public void go() {
        dim.go();
    }

    /**
     * Sets the scope to be used for script evaluation.
     */
    public void setScope(Scriptable scope) {
        setScopeProvider(IProxy.newScopeProvider(scope));
    }

    /**
     * Sets the {@link ScopeProvider} that provides a scope to be used
     * for script evaluation.
     */
    public void setScopeProvider(ScopeProvider p) {
        dim.setScopeProvider(p);
    }

    /**
     * Sets the {@link SourceProvider} that provides the source to be displayed
     * for script evaluation.
     */
    public void setSourceProvider(final SourceProvider sourceProvider) {
        dim.setSourceProvider(sourceProvider);
    }

    /**
     * Assign a Runnable object that will be invoked when the user
     * selects "Exit..." or closes the Debugger main window.
     */
    public void setExitAction(Runnable r) {
        debugGui.setExitAction(r);
    }

    /**
     * Returns an {@link InputStream} for stdin from the debugger's internal
     * Console window.
     */
    public InputStream getIn() {
        return debugGui.getConsole().getIn();
    }

    /**
     * Returns a {@link PrintStream} for stdout to the debugger's internal
     * Console window.
     */
    public PrintStream getOut() {
        return debugGui.getConsole().getOut();
    }

    /**
     * Returns a {@link PrintStream} for stderr in the Debugger's internal
     * Console window.
     */
    public PrintStream getErr() {
        return debugGui.getConsole().getErr();
    }

    /**
     * Packs the debugger GUI frame.
     */
    public void pack() {
        debugGui.pack();
    }

    /**
     * Sets the debugger GUI frame dimensions.
     */
    public void setSize(int w, int h) {
        debugGui.setSize(w, h);
    }

    /**
     * Sets the visibility of the debugger GUI frame.
     */
    public void setVisible(boolean flag) {
        debugGui.setVisible(flag);
    }

    /**
     * Returns whether the debugger GUI frame is visible.
     */
    public boolean isVisible() {
        return debugGui.isVisible();
    }

    /**
     * Frees any resources held by the debugger.
     */
    public void dispose() {
        clearAllBreakpoints();
        dim.go();
        debugGui.dispose();
        dim = null;
    }

    /**
     * Attaches the debugger to the given {@link ContextFactory}.
     */
    public void attachTo(ContextFactory factory) {
        dim.attachTo(factory);
    }

    /**
     * Detaches from the current {@link ContextFactory}.
     */
    public void detach() {
        dim.detach();
    }

    /**
     * Main entry point.  Creates a debugger attached to a Rhino
     * {@link org.mozilla.javascript.tools.shell.Main} shell session.
     */
    public static void main(String[] args) {
        Main main = new Main("Rhino JavaScript Debugger");
        main.doBreak();
        main.setExitAction(new IProxy(IProxy.EXIT_ACTION));

        System.setIn(main.getIn());
        System.setOut(main.getOut());
        System.setErr(main.getErr());

        Global global = org.mozilla.javascript.tools.shell.Main.getGlobal();
        global.setIn(main.getIn());
        global.setOut(main.getOut());
        global.setErr(main.getErr());

        main.attachTo(
            org.mozilla.javascript.tools.shell.Main.shellContextFactory);

        main.setScope(global);

        main.pack();
        main.setSize(600, 460);
        main.setVisible(true);

        org.mozilla.javascript.tools.shell.Main.exec(args);
    }

    /**
     * Entry point for embedded applications.  This method attaches
     * to the global {@link ContextFactory} with a scope of a newly
     * created {@link Global} object.  No I/O redirection is performed
     * as with {@link #main(String[])}.
     */
    public static Main mainEmbedded(String title) {
        ContextFactory factory = ContextFactory.getGlobal();
        Global global = new Global();
        global.init(factory);
        return mainEmbedded(factory, global, title);
    }

    /**
     * Entry point for embedded applications.  This method attaches
     * to the given {@link ContextFactory} with the given scope.  No
     * I/O redirection is performed as with {@link #main(String[])}.
     */
    public static Main mainEmbedded(ContextFactory factory,
                                    Scriptable scope,
                                    String title) {
        return mainEmbeddedImpl(factory, scope, title);
    }

    /**
     * Entry point for embedded applications.  This method attaches
     * to the given {@link ContextFactory} with the given scope.  No
     * I/O redirection is performed as with {@link #main(String[])}.
     */
    public static Main mainEmbedded(ContextFactory factory,
                                    ScopeProvider scopeProvider,
                                    String title) {
        return mainEmbeddedImpl(factory, scopeProvider, title);
    }

    /**
     * Helper method for {@link #mainEmbedded(String)}, etc.
     */
    private static Main mainEmbeddedImpl(ContextFactory factory,
                                         Object scopeProvider,
                                         String title) {
        if (title == null) {
            title = "Rhino JavaScript Debugger (embedded usage)";
        }
        Main main = new Main(title);
        main.doBreak();
        main.setExitAction(new IProxy(IProxy.EXIT_ACTION));

        main.attachTo(factory);
        if (scopeProvider instanceof ScopeProvider) {
            main.setScopeProvider((ScopeProvider)scopeProvider);
        } else {
            Scriptable scope = (Scriptable)scopeProvider;
            if (scope instanceof Global) {
                Global global = (Global)scope;
                global.setIn(main.getIn());
                global.setOut(main.getOut());
                global.setErr(main.getErr());
            }
            main.setScope(scope);
        }

        main.pack();
        main.setSize(600, 460);
        main.setVisible(true);
        return main;
    }

    // Deprecated methods

    /**
     * @deprecated Use {@link #setSize(int, int)} instead.
     */
    public void setSize(java.awt.Dimension dimension) {
        debugGui.setSize(dimension.width, dimension.height);
    }

    /**
     * @deprecated
     * The method does nothing and is only present for compatibility.
     */
    public void setOptimizationLevel(int level) {
    }

    /**
     * @deprecated
     * The method is only present for compatibility and should not be called.
     */
    public void contextEntered(Context cx) {
        throw new IllegalStateException();
    }

    /**
     * @deprecated
     * The method is only present for compatibility and should not be called.
     */
    public void contextExited(Context cx) {
        throw new IllegalStateException();
    }

    /**
     * @deprecated
     * The method is only present for compatibility and should not be called.
     */
    public void contextCreated(Context cx) {
        throw new IllegalStateException();
    }

    /**
     * @deprecated
     * The method is only present for compatibility and should not be called.
     */
    public void contextReleased(Context cx)
    {
        throw new IllegalStateException();
    }

    /**
     * Class to consolidate all internal implementations of interfaces
     * to avoid class generation bloat.
     */
    private static class IProxy implements Runnable, ScopeProvider {

        // Constants for 'type'.
        public static final int EXIT_ACTION = 1;
        public static final int SCOPE_PROVIDER = 2;

        /**
         * The type of interface.
         */
        private final int type;

        /**
         * The scope object to expose when {@link #type} =
         * {@link #SCOPE_PROVIDER}.
         */
        private Scriptable scope;

        /**
         * Creates a new IProxy.
         */
        public IProxy(int type) {
            this.type = type;
        }

        /**
         * Creates a new IProxy that acts as a {@link ScopeProvider}.
         */
        public static ScopeProvider newScopeProvider(Scriptable scope) {
            IProxy scopeProvider = new IProxy(SCOPE_PROVIDER);
            scopeProvider.scope = scope;
            return scopeProvider;
        }

        // ContextAction

        /**
         * Exit action.
         */
        public void run() {
            if (type != EXIT_ACTION) Kit.codeBug();
            System.exit(0);
        }

        // ScopeProvider

        /**
         * Returns the scope for script evaluations.
         */
        public Scriptable getScope() {
            if (type != SCOPE_PROVIDER) Kit.codeBug();
            if (scope == null) Kit.codeBug();
            return scope;
        }
    }
}
