/*
 * @(#)JPFApplication.java  2.3  17 September 2004
 *
 * Copyright 2004
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.jpf;

import edu.neu.ccs.*;
import edu.neu.ccs.console.*;
import edu.neu.ccs.gui.*;
import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.geom.*;
import java.io.*;
import java.lang.reflect.*;
import java.math.*;
import javax.swing.*;

/**
 * <P>This is the main application that sets up a console, a graphics window,
 * and installs a list of actions to select one of several tasks to run from
 * a given collection of "proper" methods.</P>
 *
 * @author Viera Proulx
 * @author Richard Rasala
 * @author Jason Jay Rodrigues
 * @author Jeff Raab
 * @version 2.3
 * @since 2.2
 */
class JPFApplication extends DisplayPanel
    implements JPTConstants, ConsoleAware
{   
    /** Default buffer width. */
    public static final int BUFFER_WIDTH  = 400;
    
    /** Default buffer height. */
    public static final int BUFFER_HEIGHT = 400;
    
    /** Default graphics buffered panel. */
    protected BufferedPanel window = new BufferedPanel(BUFFER_WIDTH, BUFFER_HEIGHT);
    
    /** Initializer object for the class with the methods. */
    protected JPF initializer = null;
    
    /** Class of the initializer object. */
    protected Class initializerClass = null;
    
    /** Application frame. */
    protected JPTFrame frame = null;
    
    /** Application frame title. */
    protected String frameTitle = null;
               
    /** Whether or not to show graphics window. */
    protected boolean showGraphicsWindow = true;
    
    /** RHS of main GUI panel. */
    protected DisplayPanel RHS = null;
    
    /** LHS of main GUI panel. */
    protected DisplayPanel LHS = null;
    
    /** Buttons table panel. */
    protected TablePanel buttons = null;
    
    /** Buttons panel for methods. */
    protected ActionsPanel methodButtons = null;
    
    /** Buttons panel for common actions. */
    protected ActionsPanel commonButtons = null;
    
    /** Main GUI panel. */
    protected TablePanel mainPanel = null;
    
    /** Clear graphics action. */
    protected Action clearGraphics
        = new SimpleAction("Clear Graphics") 
            { public void perform () { clearGraphics(); } };
                
    /** Toggle graphics action. */
    protected Action toggleGraphics
        = new SimpleAction("Toggle Graphics") 
            { public void perform () { toggleGraphics(); } };
                
    /** Application closing action. */
    protected Action exitFramework
        = new SimpleAction("Exit") 
            { public void perform () { exitFramework(); } };
                
    /////////////////
    // Constructor //
    /////////////////
    
    /**
     * Constructs the GUI.  
     *
     * This class must be constructed using <CODE>JPF</CODE>.
     *
     * @param JPF framework initializing this application
     * @param title title for the application frame
     */
    public JPFApplication(JPF initializer, String title) {
        if (initializer == null)
            return;
        
        this.initializer = initializer;
        this.initializerClass = initializer.getClass();
        
        setFrameTitle(title);
        
        buildGUI();
        
        frame = JPTFrame.createQuickJPTFrame(
            getFrameTitle(),
            this,
            new CenterLayout(),
            EAST);
    }
    
    
    ////////////////
    // Public API //
    ////////////////
    
    /** Returns the graphics window. */
    public BufferedPanel getGraphicsWindow() {
        return window;
    }
    
    
    /** Returns the title for the framework window. */
    public String getFrameTitle() {
        return frameTitle;
    }
    
    
    /** 
     * Sets the title for the framework window
     * to the given title.
     *
     * @param title title text for the window
     */
    public void setFrameTitle(String title) {
        frameTitle = ((title != null) && !(title.equals("")))
            ? title
            : className(initializerClass);
        
        if (frame != null)
            frame.setTitle(frameTitle);
    }
    
    
    ////////////////////////////////
    // Protected Member Functions //
    ////////////////////////////////
    
    /** Builds the buttons. */
    protected void buildButtons() {
        // build common buttons
        
        commonButtons =
            new ActionsPanel(new TableLayout(3, 1, 0, 0, CENTER, VERTICAL));
        
        JButton button;
        
        
        Color graphicsButtonColor = new Color(255, 255, 100);
        
        button = addOneButton
            (commonButtons, clearGraphics,  "Clear the Graphics Window");
        
        button.setBackground(graphicsButtonColor);
        
        button = addOneButton
            (commonButtons, toggleGraphics, "Show or Hide the Graphics Window");
        
        button.setBackground(graphicsButtonColor);
        
        
        Color exitButtonColor = Color.red;
        
        button = addOneButton
            (commonButtons, exitFramework,  "Exit the Framework");
        
        button.setBackground(exitButtonColor);
        
        
        commonButtons.uniformizeSize();
        
        
        // build method buttons
        
        methodButtons =
            new ActionsPanel(new TableLayout(1, 1, 0, 0, CENTER, VERTICAL));
        
        Color methodButtonColor = new Color(127, 255, 127);
        
        Method[] methods = getMethodList();
        int length = methods.length;
        
        for (int i = 0; i < length; i++) {
            if (methods[i] == null)
                continue;
            
            button = addOneButton(
                methodButtons,
                makeActionFromMethod(methods[i]),
                getMethodToolTip(methods[i]));
            
            button.setBackground(methodButtonColor);
        }
        
        methodButtons.uniformizeSize();
        
        
        // create scroll pane for method buttons
        
        JPTScrollPane scrollpane = new JPTScrollPane(methodButtons);
        
        // bound the scroll pane
        
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        
        int screenWidth  = (int) screenSize.getWidth();
        int screenHeight = (int) screenSize.getHeight();
        
        int marginWidth  =  50;
        int marginHeight = 250;
        
        JButton testButton     = new JButton("MyTest");
        Dimension buttonSize   = testButton.getPreferredSize();
        Dimension graphicsSize = RHS.getPreferredSize();
        Dimension commonSize   = commonButtons.getPreferredSize();
        
        int methodWidth  = screenWidth  - marginWidth  - graphicsSize.width;
        int methodHeight = screenHeight - marginHeight - commonSize.height;
        
        methodHeight -= (methodHeight % buttonSize.height);
        
        scrollpane.boundViewportPreferredSize(methodWidth, methodHeight);
        
        
        // make buttons panel
        buttons = new TablePanel(
            new Object[] { scrollpane, commonButtons },
            VERTICAL, 5, 5, CENTER);
    }
    
    
    /** Clears the graphics window. */
    protected void clearGraphics() {
        window.clearPanel();
        window.repaint();
    }
    
    
    /** Toggles the visibility of the graphics window. */
    protected void toggleGraphics() {
        if (showGraphicsWindow)
            mainPanel.remove(RHS);
        else
            mainPanel.add(RHS);
        
        showGraphicsWindow = !showGraphicsWindow;
        
        frame.pack();
        frame.setLocation(EAST);
    }
    
    
    /** Exits the framework by closing the Java Virtual Machine. */
    protected void exitFramework() {
        console.setActivated(false);
        System.exit(0); 
    }
    
    
    /**
     * Adds one button with the given action and tool tip to the panel
     * and then returns the button.
     */
    protected JButton addOneButton
        (ActionsPanel panel, Action action, String tooltip)
    {
        JButton button = panel.addAction(action);
        button.setToolTipText(tooltip);
        return button;
    }
    
    
    /** Returns a tool tip appropriate for this method. */
    protected String getMethodToolTip(Method method) {
        if (method == null)
            return "";
        
        return
            ((isStatic(method)) ? "static " : "")
            + className(method.getReturnType())
            + " "
            + method.getName()
            + "("
            + getParameterNames(method)
            + ")"
            + " in "
            + className(method.getDeclaringClass());
    }
    
    
    /** Returns a String representing the parameters of the method. */
    protected String getParameterNames(Method method) {
        String result = "";
        
        if (method == null)
            return result;
        
        Class[] types = method.getParameterTypes();
        
        int length = types.length;
        
        if (length == 0)
            return result;
        
        result = className(types[0]);
        
        for (int i = 1; i < length; i++)
            result += ", " + className(types[i]);
        
        return result;
    }
    
    
    /** Builds the main panel. */
    protected void buildGUI() {
        showConsole();
        
        // build RHS before buttons in order for its size to be
        // available
        RHS = new Display(window,  null, "Graphics");
        
        // build buttons
        buildButtons();
        
        // build LHS
        LHS = new Display(buttons, null, "Tasks");
        
        // build main panel
        mainPanel = new TablePanel(
            new Object[] { LHS, RHS }, HORIZONTAL, 5, 5, NORTH);
        
        add(mainPanel);
    }
    
    
    /** Shows the console using color text. */
    protected void showConsole() {
        console.setActivated(true);
        console.selectColorTextScheme();
    }
    
    
    /**
     * Returns a list of the "proper" methods, that is, methods for which
     * we can instantiate buttons in the main GUI.  This list may contain
     * <CODE>null</CODE>s.
     */
    protected Method[] getMethodList(){
        Method[] methods =
            getExtraMethods(initializerClass, JPF.class);
        
        int length = methods.length;
        
        for (int i = 0; i < length; i++)
            if (! (isSimpleMethod(methods[i]) || isGUIMethod(methods[i])))
                methods[i] = null;
        
        removeDuplicateVirtualMethods(methods);
        
        return methods;
    }
    
    
    /** Removes duplicate virtual methods defined in several classes. */
    protected void removeDuplicateVirtualMethods(Method[] methods) {
        if (methods == null)
            return;
        
        int length = methods.length;
        
        for (int i = (length - 1); i > 0; i--) {
            if (methods[i] == null)
                continue;
            
            if (isStatic(methods[i]))
                continue;
            
            for (int j = (i - 1); j >= 0; j--) {
                if (methods[j] == null)
                    continue;
                
                if (isStatic(methods[j]))
                    continue;
                
                if (isDuplicate(methods[i], methods[j]))
                    methods[j] = null;
            }
        }
    }
    
    
    /** Returns true if the two methods have the same signature. */
    protected boolean isDuplicate(Method method1, Method method2) {
        if ((method1 == null) || (method2 == null))
            return false;
        
        return
            method1.getName().equals(method2.getName())
            && (method1.getModifiers() == method2.getModifiers())
            && (method2.getReturnType() == method2.getReturnType())
            && isDuplicateParameterList(method1, method2);
    }
    
    
    /** Returns true if the two methods have the same parameter list. */
    protected boolean isDuplicateParameterList(Method method1, Method method2) {
        if ((method1 == null) || (method2 == null))
            return false;
        
        Class[] types1 = method1.getParameterTypes();
        Class[] types2 = method2.getParameterTypes();
        
        int length1 = types1.length;
        int length2 = types2.length;
        
        if (length1 != length2)
            return false;
        
        for (int i = 0; i <length1; i++)
            if (types1[i] != types2[i])
                return false;
        
        return true;
    }
    
    
    /** Makes a ThreadedAction from the given method. */
    protected Action makeActionFromMethod(final Method method) {
        if (method == null)
            return null;
        
        final String name = (isStatic(method))
            ? className(method.getDeclaringClass()) + "." + method.getName()
            : method.getName();
        
        return new ThreadedAction(
            new SimpleAction(name) {
                public void perform () { performAction(method); }
            });    
    }
    
    
    /**
     * Performs the given method and
     * assures that the method calls are synchronized.
     */
    protected synchronized void performAction(Method method){
        String name = method.getName();
        
        try {
            if (isGUIMethod(method))
                performActionUsingGUI(method);
            else if (isStatic(method))
                method.invoke(null, null);
            else
                method.invoke(initializer, null);
        }
        catch (Exception exception) {
            handleMethodException(exception, name);
        }
    }
    
    
    /** Handles exception produced by Method invocation. */
    protected void handleMethodException(Throwable exception, String name) {
    
        if (exception instanceof IllegalAccessException){
            IllegalAccessException illAccEx
                = (IllegalAccessException) exception;
            
            console.err.print  ("JPF Error: IllegalAccessException: ");
            console.err.println(illAccEx + "\n"); 
        }
        else if (exception instanceof IllegalArgumentException){
            IllegalArgumentException illArgEx
                = (IllegalArgumentException) exception;
            
            console.err.print  ("JPF Error: IllegalArgumentException: ");
            console.err.println(illArgEx + "\n");
        }
        else if (exception instanceof InvocationTargetException){
            InvocationTargetException invTarEx
                = (InvocationTargetException) exception;
            
            exception = invTarEx.getTargetException();
        }
        
        printExceptionTrace(exception, name);
    }
    
    
    /** Prints the exception trace minus references to Java reflection. */
    protected void printExceptionTrace(Throwable exception, String name) {
        console.err.println
            ("Exception: " + exception + "\nIn: " + name + "\n");
            
        StringWriter sWriter = new StringWriter();
        PrintWriter  pWriter = new PrintWriter(sWriter, true);
        
        exception.printStackTrace(pWriter);
        
        String message = sWriter.toString();
        
        int index = message.indexOf(".reflect.");
        
        if (index >= 0)
            message = message.substring(0, index);
        
        index = message.lastIndexOf("\n");
        
        if (index >= 0)
            message = message.substring(0, index);
        
        if (message.length() > 0)
            console.err.println(message + "\n");
    }
    
    
    /**
     * Performs the given method using an automatice GUI and
     * assures that the method calls are synchronized.
     */
    public synchronized void performActionUsingGUI(Method method){
        String    title = method.getName() + " evaluator";
        
        MethodGUI gui   = new MethodGUI(this, method);
        
        JPTFrame  frame = JPTFrame.createQuickJPTFrame
            (title, gui, new CenterLayout(), CENTER);
        
        JRootPane pane  = gui.getRootPane();
        
        if (pane != null)
            pane.setDefaultButton(gui.evaluateButton);
    }
    
    
    /** Returns true if the method should be converted to a simple button. */
    protected boolean isSimpleMethod(Method method) {
        if (method == null)                             // no method passed
            return false;
        
        if (method.getName().equals("main"))            // exclude any main
            return false;
        
        if (method.getReturnType() != void.class)       // method is not void
            return false;
        
        if (method.getParameterTypes().length > 0)      // method has parameters
            return false;
        
        int modifiers = method.getModifiers();
            
        return (modifiers & Modifier.PUBLIC) != 0;      // method is public
    }
    
    
    /** Returns true if the method should be converted to a button with GUI. */
    protected boolean isGUIMethod(Method method) {
        if (method == null)                             // no method passed
            return false;
        
        if (method.getName().equals("main"))            // exclude any main
            return false;
        
        if (! isAcceptableMethodForGUI(method))         // exclude methods that
            return false;                               // we cannot handle in GUI
        
        int modifiers = method.getModifiers();
            
        return (modifiers & Modifier.PUBLIC) != 0;       // method is public
    }
    
    
    /** Returns true if the type is acceptable for a method parameter. */
    protected boolean isAcceptableTypeForParameter(Class c) {
        if (c == null)
            return false;
        
        if (c.isPrimitive())
            return true;
        
        if (Stringable.class.isAssignableFrom(c))
            return true;
        
        if (c.equals(String.class))
            return true;
        
        if (c.equals(Color.class))
            return true;
        
        if (c.equals(BigInteger.class))
            return true;
        
        if (c.equals(BigDecimal.class))
            return true;
        
        if (c.equals(Point2D.Double.class))
            return true;
        
        return false;
    }
    
    
    /** Returns true if the type is acceptable for a method return. */
    protected boolean isAcceptableTypeForReturn(Class c) {
        if (c == null)
            return false;
        
        if (isAcceptableTypeForParameter(c))
            return true;
        
        if (Paint.class.isAssignableFrom(c))
            return true;
        
        return false;
    }
    
    
    /** 
     * Returns true if the parameter types and return type
     * are acceptable for the automatic MethodGUI and if
     * the method is either non-void or has parameters.
     */
    protected boolean isAcceptableMethodForGUI(Method method) {
        if (method == null)
            return false;
        
        Class returnType = method.getReturnType();
        Class[] parameterTypes = method.getParameterTypes();
        
        int length = parameterTypes.length;
        
        if (returnType.equals(void.class) && (length == 0))
            return false;
        
        for (int i = 0; i < length; i++)
            if (! isAcceptableTypeForParameter(parameterTypes[i]))
                return false;
        
        return returnType.equals(void.class) || isAcceptableTypeForReturn(returnType);
    }
    
    
    /** Gets the methods in Class c that do not come from Class d. */
    protected Method[] getExtraMethods(Class c, Class d) {
        if ((c == null)
            || (c.equals(d))
            || (c.isPrimitive())
            || (c.isInterface())
            || (c.isArray()))
                return new Method[] { };
        
        return joinMethodArrays(
            getExtraMethods(c.getSuperclass(), d),
            c.getDeclaredMethods());
    }
    
    
    /** Joins two Method arrays into one. */
    protected Method[] joinMethodArrays(Method[] listA, Method[] listB){
    
        int lengthA = listA.length;
        int lengthB = listB.length;
        int length = lengthA + lengthB;
        
        Method[] list = new Method[length];
        
        for (int i = 0; i < lengthA; i++)
            list[i] = listA[i];
        
        for (int i = lengthA; i < length; i++)
            list[i] = listB[i - lengthA];
        
        return list;
    }
    
    
    /* Returns whether or not the method is static. */
    protected boolean isStatic(Method method) {
        if (method == null)
            return false;
        
        int modifiers = method.getModifiers();
        
        return (modifiers & Modifier.STATIC) != 0;
    }
    
    
    /** Returns the unqualified name of the given Class */
    protected static String className(Class c) {
        String s = c.getName();
        
        int pos = s.lastIndexOf(".");
        
        if (pos < 0)
            return s;
        
        return s.substring(pos + 1);
    }
}

