/* -------------------------------------------------------------------------- *
 * OpenSim: gui.java                                                          *
 * -------------------------------------------------------------------------- *
 * OpenSim is a toolkit for musculoskeletal modeling and simulation,          *
 * developed as an open source project by a worldwide community. Development  *
 * and support is coordinated from Stanford University, with funding from the *
 * U.S. NIH and DARPA. See http://opensim.stanford.edu and the README file    *
 * for more information including specific grant numbers.                     *
 *                                                                            *
 * Copyright (c) 2005-2017 Stanford University and the Authors                *
 * Author(s): Ayman Habib, Kevin Xu                                           *
 *                                                                            *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0          *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 * -------------------------------------------------------------------------- */

package org.opensim.console;

import java.io.File;
import java.io.IOException;
import java.util.prefs.Preferences;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import org.openide.ErrorManager;
import org.openide.cookies.InstanceCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
import org.openide.util.Exceptions;
import org.openide.util.actions.CallableSystemAction;
import org.opensim.customGui.InstructionsTopComponent;
import org.opensim.customGui.ParametersTopComponent;
import org.opensim.modeling.Body;
import org.opensim.modeling.Constraint;
import org.opensim.modeling.Coordinate;
import org.opensim.modeling.Force;
import org.opensim.modeling.Joint;
import org.opensim.modeling.Marker;
import org.opensim.modeling.Model;
import org.opensim.modeling.OpenSimContext;
import org.opensim.modeling.OpenSimObject;
import org.opensim.modeling.Probe;
import org.opensim.modeling.State;
import org.opensim.modeling.Storage;
import org.opensim.tracking.tools.ToolbarSimulationAction;
import org.opensim.utils.ErrorDialog;
import org.opensim.utils.TheApp;
import org.opensim.view.motions.MotionsDB;
import org.opensim.view.pub.OpenSimDB;
import org.opensim.view.pub.ViewDB;

/**
 *  gui is an implementation of the Facade design pattern to 
 * 
 * 1. Shield the user from the various classes used by the GUI
 * 2. Provide convenience methods that update the GUI if needed
 * 
 * Methods in this class can be used without specifying "gui." for convenience.
 * 
 * @author Ayman
 */
public final class gui {
    /**
     * getCurrentModel() gets a reference to the model that is current in the OpenSim application.
     * 
     * @return current model
     */
    static public Model getCurrentModel(){
        return OpenSimDB.getInstance().getCurrentModel();
    }
    /**
     * setCurrentModel, makes a model that is loaded in the OpenSim application current.
     * 
     * @param aModel to be made current
     */
    static public void setCurrentModel(Model aModel){
        OpenSimDB.getInstance().setCurrentModel(aModel);
    }
    
    /**
     * dumpModelState() dumps the vector of continuous state variables that backs aModel
     * as a String (SimTK::State.getY())
     */
    static public String dumpModelState(Model aModel) {
        State stateRef = OpenSimDB.getInstance().getContext(aModel).getCurrentStateRef();
        return stateRef.getY().toString();
    }

    /**
     * getStateRef() returns a reference to the instance of SimTK::State that backs aModel
     */
    static public State getStateRef(Model aModel) {
        State stateRef = OpenSimDB.getInstance().getContext(aModel).getCurrentStateRef();
        return stateRef;
    }
    /**
     * getCurrentMotion() gets a reference to the motion that is current in the OpenSim application.
     * 
     * @return current motion as a Storage object. If none or multiple (sync.) it returns null
     */
    static public Storage getCurrentMotion(){
        if (MotionsDB.getInstance().getNumCurrentMotions()!=1) return null;
        return MotionsDB.getInstance().getCurrentMotion(0).motion;
    }
 
    /**
     * getCoordinate ()
     * 
     * @param aModel
     * @param name
     * @return a reference to the coordinate with passed in name in the model "aModel"
     */
    static public Coordinate getCoordinate(Model aModel, String name){
        return aModel.getCoordinateSet().get(name);
    }
    /**
     * setCoordinateValue() allows the user to set the value of the passed in Coordinate to the specified newValue
     *  This call, also updates the Graphics window if needed.
     *  rotational coordinates should be specified in radians
     * 
     * @param coordinate
     * @param newValue 
     */
    static public void setCoordinateValue(final Coordinate coordinate, final double newValue){
        OpenSimContext context=OpenSimDB.getInstance().getContext(coordinate.getModel());
        coordinate.setValue(context.getCurrentStateRef(), newValue);
        SwingUtilities.invokeLater(new Runnable(){

            @Override
            public void run() {
                ViewDB.getInstance().updateModelDisplay(coordinate.getModel());
                MotionsDB.getInstance().reportTimeChange(0);
            }
        });
     }
    /**
     * setCoordinateValueDegrees allows the user to set the value of the passed in Coordinate to the specified newValue
     *  This call, also updates the Graphics window if needed.
     * 
     * @param coordinate
     * @param newValue specified in degrees (rather than radians)
     */
    static public void setCoordinateValueDegrees(final Coordinate coordinate, final double newValue){
        double valueInDegrees = newValue*Math.PI/180.;
        setCoordinateValue(coordinate, valueInDegrees);
     }
    /**
     * addModel() creates a new OpenSim model from the passed in fileName and loads this model 
     * into the OpenSim application. This is equivalent to "File->Open Model..."
     * 
     * @param fileName to construct the model from
     * 
     * @Deprecated use loadModel instead
     */
    static public void addModel(String fileName){
        loadModel(fileName);
    }
    /**
     * loadModel() creates a new OpenSim model from the passed in fileName and loads this model 
     * into the OpenSim application. This is equivalent to "File->Open Model..."
     * 
     * @param fileName to construct the model from
     * 
     * 
     */
    static public void loadModel(String fileName){
        try {
            Model aModel = new Model(fileName);
            OpenSimDB.getInstance().addModel(aModel);
        } catch (IOException ex) {
            ErrorDialog.displayExceptionDialog(ex);
        }
    }
    /**
     * loadModel() loads the passed in model into the OpenSim application. 
     * The application takes ownership of the passed in model
     * 
     * @param aModel is the model to be loaded. 
     * 
     * 
     */
    static public void loadModel(Model aModel){
        try {
            aModel.markAdopted();
            OpenSimDB.getInstance().addModel(aModel);
        } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
        }
    }
    /**
     * loadMotion constructs a motion out of the passed in fileName and loads it into the application, 
     * associating it with the current model if possible. Equivalent to "File->Load Motion..."
     * 
     * @param fileName to construct the motion from
     */
    static public void loadMotion(String fileName){
        MotionsDB.getInstance().loadMotionFile(fileName, true);
    }
    /**
     * Generic method to invoke commands available from the menu bar. The actionName passed in is usually
     * the concatentaion of the words making the menu items:
     * e.g. performAction("FileOpen") is equivalent to picking the cascade menu File->Open Model
     * 
     * @param actionName
     */
    static public void performAction(String actionName) {
        FileObject myActionsFolder = FileUtil.getConfigFile("Actions/Edit");
        FileObject[] myActionsFolderKids = myActionsFolder.getChildren();
        for (FileObject fileObject : myActionsFolderKids) {
            //Probably want to make this more robust,
            //but the point is that here we find a particular Action:
            if (fileObject.getName().contains(actionName)) {
                try {
                    DataObject dob = DataObject.find(fileObject);
                    InstanceCookie ic = dob.getLookup().lookup(InstanceCookie.class);
                    if (ic != null) {
                        Object instance = ic.instanceCreate();
                        if (instance instanceof CallableSystemAction) {
                            CallableSystemAction a = (CallableSystemAction) instance;
                            a.performAction();
                        }
                    }
                    break;
                } catch (Exception e) {
                    ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
                 }
            }
        }

    }
    /**
     * findObject locates an Object in the model and returns a reference to it.
     * Beware, since this gives unguarded access to the objects in the model, changes made the object
     * may not propagate throughout the model and or application. USe at your own risk.
     * 
     * @param aModel
     * @param type : one of Body, Joint, Force (or any force producing object e.g. muscle), Controller
     * @param name : name of the object of the specified type 
     * @return Object in the model with specified type and name 
     * @throws IOException if the object couldn't be located
     */
    static public OpenSimObject findObject(Model aModel, String type, String name) throws IOException
    {
        OpenSimObject rawObject =  aModel.getObjectByTypeAndName(type, name);
        // Will try to upcast to type "type" if possible. Ideally there's a generic mechanism
        // for now we'll go with ugly if else since RTTI info is not carried over across SWIG
        if (type.equalsIgnoreCase("Body"))
            return Body.safeDownCast(rawObject);
        else if (type.equalsIgnoreCase("Force"))
            return Force.safeDownCast(rawObject);
        else if (type.equalsIgnoreCase("Constraint"))
            return Constraint.safeDownCast(rawObject);
        else if (type.equalsIgnoreCase("Coordinate"))
            return Coordinate.safeDownCast(rawObject);
        else if (type.equalsIgnoreCase("Marker"))
            return Marker.safeDownCast(rawObject);
        else if (type.equalsIgnoreCase("Joint"))
            return Joint.safeDownCast(rawObject);
        else if (type.equalsIgnoreCase("Probe"))
            return Probe.safeDownCast(rawObject);
        return rawObject;
    }
    /**
     * sleectObject marks the passed in object as selected in the application if it has a visual representation
     * 
     * @param obj 
     */
    static public void selectObject(final OpenSimObject obj){
        SwingUtilities.invokeLater(new Runnable(){

            @Override
            public void run() {
                ViewDB.getInstance().setSelectedObject(obj);
            }
        });    
    }
    /**
     * setObjectColor marks the passed in object if it has a visual representation with passed in color
     * 
     * @param obj 
     */
    static public void setObjectColor(final OpenSimObject obj,  final double[] colorComponents){
        SwingUtilities.invokeLater(new Runnable(){

            @Override
            public void run() {
                ViewDB.getInstance().setObjectColor(obj, colorComponents);
            }
        });
        
    }
    /**
     * Turn on/off the display of the passed in object according to the passed in flag
     * Valid only for objects can be shown/hidden from the navigator
     * 
     * @param obj
     * @param onOrOff 
     */
    static public void toggleObjectDisplay(final OpenSimObject obj,  final boolean onOrOff){
        SwingUtilities.invokeLater(new Runnable(){

            @Override
            public void run() {
                ViewDB.getInstance().toggleObjectsDisplay(obj, onOrOff);
            }
        });
    }
    /**
     * Set the opacity of an object. Valid only for objects that allow that operation, 
     * specifically those that have visual representation and athat have individual control over
     * their opacity
     * 
     * @param obj The object to set the opacity of
     * @param newOpacity0To1 
     */
    static public void setObjectOpacity(final OpenSimObject obj, final double newOpacity0To1) {
        SwingUtilities.invokeLater(new Runnable(){

            @Override
            public void run() {
                ViewDB.getInstance().setObjectOpacity(obj, newOpacity0To1);
            }});
    }
    /**
     * Perform operation in current graphics window equivalent to pressing c while the window has focus
     * @param c 
     */
    static public void gfxWindowSendKey(final char c){
    }
    /**
     * Get the full name of the directory used as a root for the Scripts.
     * @return string that represents the path to the Scripts directory
     */
    static public String getScriptsPath()
    {
        String relativePath= TheApp.getCurrentVersionPreferences().get("Paths: Scripts Path", "Scripts");
        if (relativePath.equals(null)) return null;
        return new File(relativePath).getAbsolutePath();
    }
    /**
     * Get the full name of the directory used as a root for OpenSim installation.
     * @return string that represents the path to the installation directory
     */
    static public String getInstallDir()
    {
        return TheApp.getInstallDir();
    }
    
    /**
     * Invoke method to install resources to user directory
     */
    static public String installResources()
    {
        return TheApp.installResources();
    }
    /**
     * @return the full path to the directory used to install Resources (Models, CodeExamples)
     */
    static public String getResourcesDir()
    {
        return TheApp.getResourcesDir();
    }
    /**
     * getClassName() returns the full qualified name of the Class that obj is an instance of
     * @param obj
     * @return class name as a string, fully qualified.
     */
    static public String getClassName(Object obj)
    {
        return obj.getClass().getName();
    }
    /**
     * methodsview() Shows in a standalone modal dialog the methods available for the passed in object
     * 
     * @param obj
     * 
     */
    static public void methodsview(Object obj)
    {
        methodsview(obj.getClass());
    }
    /**
     * Show in a standalone modal dialog the methods available for the passed in class
     * Class name is fully qualified with package name etc.
     * @param classObj 
     */
    static public void methodsview(Class classObj)
    {
        String methodsList = ListMethods.listMethodsForClass(classObj.getName());
        // Make a scroll pane and stick methodsList in it.
        JFrame methodsFrame = new JFrame();
        JScrollPane scrollPane = new JScrollPane();
        JTextArea textArea = new JTextArea(methodsList);
        scrollPane.setViewportView(textArea);
        methodsFrame.getContentPane().add(scrollPane);
        methodsFrame.setSize(400, 700);
        methodsFrame.setVisible(true);
        return ;
    }
    /**
     * getOpenModels()
     * 
     * @return list of models currently loaded in the application
     */
    static public Model[] getOpenModels() 
    {
        Object[] modelsAsObjects = OpenSimDB.getAllModels();
        Model[] models = new Model[modelsAsObjects.length];
        for(int i=0; i<modelsAsObjects.length; i++)
            models[i] = (Model) modelsAsObjects[i];
        return models;
    } 
     
    /**
     * updateDisplay to be in sync. with model edits done from scripting shell. 
     * As of now works on Navigator for Probes only, will need to update Coordinates and Gfx window
     * and support other object types as well
     */
    static public void updateDisplay() {
        ViewDB.getInstance().updateModelDisplay(getCurrentModel());
    }
    /**
     * createParametersWindow Creates and open a window for custom GUI, the window is a singleton type (1 per application)
     * and is normally docked. You can close() and open() it as needed. It should be treated
     * as a JComponent (in Java Swing terms)
     * 
     * @return handle to the opened window
     */
    static public ParametersTopComponent createParametersWindow()
    {
        ParametersTopComponent win = ParametersTopComponent.findInstance();
        win.open();
        return win;
    }
        
    /**
     * createInstructionsWindow Creates and open a window for Instructions, the window is a singleton type (1 per application)
     * and is normally docked. You can close() and open() it as needed. It should be treated
     * as a JComponent (in Java Swing terms)
     * The function takes in a full path name for an html document to display on opening.
     * 
     * @return handle to the opened window
     */
    static public InstructionsTopComponent createInstructionsWindow(String fileName)
    {
        InstructionsTopComponent win = InstructionsTopComponent.findInstance();
        win.setDocument(fileName);
        win.open();
        return win;
    }
    static public void setSimulationToolBarVisibility(boolean show) {
        try {
            ToolbarSimulationAction simAct = (ToolbarSimulationAction) ToolbarSimulationAction.findObject(
                        (Class)Class.forName("org.opensim.tracking.tools.ToolbarSimulationAction"), false);
            simAct.setToolbarVisiblity(show);
        } catch (ClassNotFoundException ex) {
            Exceptions.printStackTrace(ex);
        }
    }
}