/*
 * @(#)MethodGUI.java  1.0  16 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.gui.*;
import edu.neu.ccs.codec.*;
import edu.neu.ccs.console.*;
import edu.neu.ccs.filter.*;
import edu.neu.ccs.parser.*;
import edu.neu.ccs.pedagogy.*;
import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.font.*;
import java.util.*;
import java.text.*;
import java.math.*;
import javax.swing.*;
import java.lang.reflect.*;

/**
 * This class implements the GUI for a Method that is non-trivial,
 * that is, has a return value and/or non-trivial parameters.
 *
 * @author Viera Proulx
 * @author Richard Rasala
 * @version 2.3
 * @since 2.2
 */
class MethodGUI extends TablePanel
    implements JPTConstants, ConsoleAware
{   
    /** The associated JPFApplication.*/
    JPFApplication application = null;
    
    /** The method used to define this MethodGUI. */
    protected Method method = null;
    
    // Method Information
    
    /** The method name. */
    protected String name = null;
    
    /** The return type. */
    protected Class returnType = null;
    
    /** The return type name. */
    protected String returnTypeName = null;
    
    /** The parameter types. */
    protected Class[] parameterTypes = null;
    
    /** The parameter type names. */
    protected String[] parameterTypeNames = null;
    
    // GUI Information
    
    /** The MethodGUI orientation. */
    protected int orientation = HORIZONTAL;
    
    /** The parameter views. */
    protected TypedView[] parameterViews = null;
    
    /** The return view for everything but Paint. */
    protected TypedView returnView = null;
    
    /** The return view for Paint. */
    protected PaintSwatch paintView = null;
    
    /** The evaluate action. */
    protected Action evaluate = null;
    
    /** The evaluate button. */
    protected JButton evaluateButton = null;
    
    /** The minimum text field view width. */
    private static int minTFVWidth = 160;
    
    /** The text field view width. */
    private static int TFVWidth = 0;
    
    /** The maximum number of columns. */
    private static int maxCols = 0;
    
    /** The gap between cells in the GUI. */
    private static int gapSize = 10;
    
    /** The size of a paint swatch. */
    private static int swatchSize = 48;
    
    
    //////////////////
    // Constructors //
    //////////////////
    
    /** Constructs the MethodGUI using the given Method. */
    protected MethodGUI(JPFApplication application, Method method) {
        this(application, method, HORIZONTAL);
    }
    
    
    /** Constructs the MethodGUI using the given Method and orientation. */
    protected MethodGUI(JPFApplication application, Method method, int orientation) {
        super(0, 0, gapSize, gapSize, CENTER);
        
        if (method == null)
            return;
        
        setSizeParameters();
        
        this.application = application;
        this.method = method;
        this.orientation = orientation;
        
        extractInformation();
        createEvaluate();
        buildTable();
    }
    
    
    ///////////////////////
    // Protected Methods //
    ///////////////////////
    
    /** Extract the important information about the method. */
    protected void extractInformation() {
        name = method.getName();
        
        returnType = method.getReturnType();
        
        returnTypeName = application.className(returnType);
        
        parameterTypes = method.getParameterTypes();
        
        parameterTypeNames = new String[parameterTypes.length];
        
        int length = parameterTypes.length;
        
        for (int i = 0; i < length; i++){
            parameterTypeNames[i] = application.className(parameterTypes[i]);
        }
    }
    
    
    /** Build the GUI TablePanel. */
    protected void buildTable() {
        // make sure that orientation has a valid value
        if (orientation != VERTICAL)
            orientation = HORIZONTAL;
        
        // define rows and cols for default orientation of HORIZONTAL
        int rows = 3;
        int cols = parameterTypes.length
            + 1
            + (returnType.equals(void.class) ? 0 : 1);
        
        // if there are too many cols, make the orientation VERTICAL
        if (cols > maxCols)
            orientation = VERTICAL;
        
        // if orientation is VERTICAL then interchange rows and cols
        if (orientation == VERTICAL) {
            int temp = cols;
            cols = rows;
            rows = temp;
        }
        
        // now set the rows and cols of the table
        setRows(rows);
        setColumns(cols);
        
        buildParameterViews();
        buildReturnView();
        
        if (orientation == VERTICAL)
            buildVerticalGUI();
        else
            buildHorizontalGUI();
    }
    
    
    /** Install the views in the GUI using a vertical arrangement. */
    protected void buildVerticalGUI() {
        int length = parameterTypes.length;
        int row;
        
        for (row = 0; row < length; row++) {
            addObject(parameterTypeNames[row],       row, 0);
            addObject(createInputIcon(),             row, 1);
            addObject(new Halo(parameterViews[row]), row, 2);
        }
        
        row = length;
        
        addObject("method",           row, 0);
        addObject(createActionIcon(), row, 1);
        
        evaluateButton  = (JButton)
        addObject(evaluate,           row, 2);
        
        if (! returnType.equals(void.class)) {
            row = length + 1;
            
            addObject(returnTypeName,       row, 0);
            addObject(createOutputIcon(),   row, 1);
            addObject(getReturnView(),      row, 2);
        }
    }
    
    
    /** Install the views in the GUI using a horizontal arrangement. */
    protected void buildHorizontalGUI() {
        int length = parameterTypes.length;
        int next = 0;
        int col;
        
        if (! returnType.equals(void.class)) {
            addObject(returnTypeName,       0, 0);
            addObject(createOutputIcon(),   1, 0);
            addObject(getReturnView(),      2, 0);
            
            next = 1;
        }
        
        col = next;
        
        addObject("method",           0, col);
        addObject(createActionIcon(), 1, col);
        
        evaluateButton  = (JButton)
        addObject(evaluate,           2, col);
        
        next++;
        
        for (col = 0; col < length; col++) {
            addObject(parameterTypeNames[col],       0, col + next);
            addObject(createInputIcon(),             1, col + next);
            addObject(new Halo(parameterViews[col]), 2, col + next);
        }
    }
    
    
    /**
     * Return a typed view appropriate for the given class
     * that may do input or return values.
     */
    protected TypedView createView(Class c) {
        if (c == null)
            return null;
        
        if (c.equals(Color.class) || c.equals(XColor.class)) {
            ColorView view = new ColorView(Color.black, true);
            view.setChooserClickCount(1);
            return view;
        }
        
        TextFieldView tfv
            = new TextFieldView("", getErrorPrompt(c), "InputError", TFVWidth);
        
        if (Stringable.class.isAssignableFrom(c))
            tfv.setDataType(c);
        
        else if (c.isPrimitive()) {
            if (c.equals(byte.class))
                tfv.setDataType(XByte.class);
            else if (c.equals(short.class))
                tfv.setDataType(XShort.class);
            else if (c.equals(int.class))
                tfv.setDataType(XInt.class);
            else if (c.equals(long.class))
                tfv.setDataType(XLong.class);
            else if (c.equals(float.class))
                tfv.setDataType(XFloat.class);
            else if (c.equals(double.class))
                tfv.setDataType(XDouble.class);
            else if (c.equals(char.class))
                tfv.setDataType(XChar.class);
            else if (c.equals(boolean.class))
                tfv.setDataType(XBoolean.class);
        }
        
        else if (c.equals(String.class))
            tfv.setDataType(XString.class);
        
        else if (c.equals(BigInteger.class))
            tfv.setDataType(XBigInteger.class);
        
        else if (c.equals(BigDecimal.class))
            tfv.setDataType(XBigDecimal.class);
        
        else if (c.equals(Point2D.Double.class))
            tfv.setDataType(XPoint2D.class);
        
        return tfv;
    }
    
    
    /**
     * Return a typed view appropriate for the given class
     * that is specific for return values.
     */
    protected void createReturnView(Class c) {
        /*
        if (c == null)
            return null;
        
        if (Paint.class.isAssignableFrom(c))
            return new PaintSwatch(Colors.Transparent, swatchSize, swatchSize);
        
        return createView(c);
        */
    }
    
    
    /** Build the array of views for the method parameters. */
    protected void buildParameterViews() {
        int length = parameterTypes.length;
        
        parameterViews = new TypedView[length];
        
        for (int i = 0; i < length; i++)
            parameterViews[i] = createView(parameterTypes[i]);
    }
    
    
    /**
     * Build the view for the return value.
     *
     * If the return type is assignable to Paint, then build a paint swatch
     * otherwise call createView to build the TypedView return view.
     */
    protected void buildReturnView() {
        if (Paint.class.isAssignableFrom(returnType))
            paintView = new PaintSwatch(Colors.Transparent, swatchSize, swatchSize);
        else
            returnView = createView(returnType);
    }
    
    /** Return the current view for the return type object. */
    protected Object getReturnView() {
        if (returnView != null)
            return returnView;
        else
            return paintView;
    }
    
    /** Return an error prompt customized for the given class. */
    protected String getErrorPrompt(Class c) {
        return "Error in data of type " + application.className(c);
    }
    
    
    protected JComponent createInputIcon() {
        return new ActivityIcon.InputIcon();
    }
    
    
    protected JComponent createActionIcon() {
        return new ActivityIcon.ActionIcon();
    }
    
    
    protected JComponent createOutputIcon() {
        return new ActivityIcon.OutputIcon();
    }
    
    
    /**
     * Return the user input as an object
     * from the typed view being used to
     * obtain data for the given class.
     */
    protected Object extractParameterValue(TypedView view, Class c) {
        if (view == null)
            return null;
        
        Object object = view.demandObject();
        
        if (object instanceof XByte) {
            if (c.equals(byte.class))
                object = new Byte       (((XByte)object)   .getValue());
        }
        
        else
        if (object instanceof XShort){
            if (c.equals(short.class))
                object = new Short      (((XShort)object)  .getValue());
        }
        
        else
        if (object instanceof XInt) {
            if (c.equals(int.class))
                object = new Integer    (((XInt)object)        .getValue());
        }
        
        else
        if (object instanceof XLong) {
            if (c.equals(long.class))
                object = new Long       (((XLong)object)       .getValue());
        }
        
        else
        if (object instanceof XFloat) {
            if (c.equals(float.class))
                object = new Float      (((XFloat)object)      .getValue());
        }
        
        else
        if (object instanceof XDouble) {
            if (c.equals(double.class))
                object = new Double     (((XDouble)object)     .getValue());
        }
        
        else
        if (object instanceof XChar) {
            if (c.equals(char.class))
                object = new Character  (((XChar)object)       .getValue());
        }
        
        else
        if (object instanceof XBoolean) {
            if (c.equals(boolean.class))
                object = new Boolean    (((XBoolean)object)    .getValue());
        }
        
        else
        if (object instanceof XString) {
            if (c.equals(String.class))
                object = new String     (((XString)object)     .getValue());
        }
        
        else
        if (object instanceof XColor) {
            if (c.equals(Color.class))
                object = ((XColor)object).getValue();
        }
        
        else
        if (object instanceof XBigInteger) {
            if (c.equals(BigInteger.class))
                object = ((XBigInteger)object) .getValue();
        }
        
        else
        if (object instanceof XBigDecimal) {
            if (c.equals(BigDecimal.class))
                object = ((XBigDecimal)object) .getValue();
        }
        
        return object;
    }
    
    
    /** Return the array of user input values from the parameter views. */
    protected Object[] extractParameterValues() {
        int length = parameterViews.length;
        
        Object[] values = new Object[length];
        
        for (int i = 0; i < length; i++)
            values[i] = extractParameterValue(parameterViews[i], parameterTypes[i]);
        
        return values;
    }
    
    
    /**
     * Display the return value in the return view.
     * For most types, this will display the result of the toString() method.
     * Color and Paint are handled as a special case.
     */
    protected void showReturnValue(Object value) {
        if (value == null) {
            if (returnView != null)
                returnView.setViewState("null");
            else
                paintView.setPaint(Colors.Transparent);
            
            return;
        }
        
        // handle Color and Paint as a special case
        
        // convert XColor to Color which is a Paint
        if (value instanceof XColor)
            value = ((XColor) value).getValue();
        
        // handle Paint
        if (value instanceof Paint) {
            paintView.setPaint((Paint) value);
            return;
        }
        
        // convert Point2D.Double to XPoint2D to obtain nicer toString method
        if (value instanceof Point2D.Double) {
            Point2D.Double p = (Point2D.Double) value;
            value = new XPoint2D(p.getX(), p.getY());
        }
        
        returnView.setViewState(value.toString());
    }
    
    
    /** Create the evaluate action for the Evaluate button in the GUI. */
    protected void createEvaluate() {
        evaluate = new SimpleAction(name) {
            public void perform() {
                synchronized(application) {
                    evaluate();
                }
            }
        };
    }
    
    
    /** The evaluate method executed by the evaluate action. */
    protected void evaluate() {
        if (returnType.equals(void.class))
            evaluateVoid();
        else
            evaluateWithReturn();
    }
    
    
    /** The evaluate method in the case of void return. */
    protected void evaluateVoid() {
        try {
            if (application.isStatic(method))
                method.invoke(null, extractParameterValues());
            else
                method.invoke(application.initializer, extractParameterValues());
        }
        catch (Exception exception) {
            application.handleMethodException(exception, name);
        }
    }
    
    
    /** The evaluate method in the case of non-void return. */
    protected void evaluateWithReturn() {
        Object value = null;
        
        try {
            if (application.isStatic(method))
                value
                    = method.invoke(null, extractParameterValues());
            else
                value
                    = method.invoke(application.initializer, extractParameterValues());
            
            showReturnValue(value);
        }
        catch (Exception exception) {
            application.handleMethodException(exception, name);
        }
    }
    
    
    /** Set the text field view width and the maximum columns. */
    protected void setSizeParameters() {
        if (TFVWidth > 0)
            return;
        
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        int screenWidth = screenSize.width;
        
        // "0000000000000000000000000" is 25 numeric characters
        int fieldWidth = TextFieldView.getSampleWidth("0000000000000000000000000");
        
        ColorView colorview = new ColorView(null, true);
        Dimension colorSize = colorview.getPreferredSize();
        int colorWidth = colorSize.width;
        
        TFVWidth = minTFVWidth;
        TFVWidth = Math.max(TFVWidth, fieldWidth);
        
        int maxWidth = Math.max(TFVWidth, colorWidth);
        
        maxCols = screenWidth / maxWidth;
    }
    
}
