/* 
 * JGimp - A Java extension for the GIMP
 * Copyright (C) 2002  Georgia Tech Research Corp.
 * Written by Michael Terry, mterry@cc.gatech.edu
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package org.gimp.jgimp.jython;

import java.io.File;
import java.util.Properties;
import java.util.Vector;
import java.util.regex.Pattern;

import javax.swing.JOptionPane;

import org.gimp.jgimp.GimpApp;
import org.gimp.jgimp.JGimpData;
import org.gimp.jgimp.extension.JGimpExtension;
import org.gimp.jgimp.plugin.JGimpParamDescriptor;
import org.gimp.jgimp.plugin.JGimpPlugIn;
import org.gimp.jgimp.plugin.JGimpPlugInReturnValues;
import org.gimp.jgimp.plugin.JGimpProcedureDescriptor;
import org.gimp.jgimp.proxy.JGimpProxy;
import org.python.core.PySystemState;
import org.python.util.PythonInterpreter;

/**
 * The JythonInterpreter for JGimp (non-GUI parts).
 */
public class JGimpJythonInterpreter implements JGimpExtension, JGimpPlugIn {
    
    private final static String JYTHON_PLUGIN_DIR_KEY = "jython_plugin_dir";
    private final static String JYTHON_EXTENSION_DIR_KEY = "jython_extension_dir";
    private static JGimpJythonInterpreter s_Instance = null;
    private PythonInterpreter m_Interpreter = null;
    private InteractiveJythonInterpreter m_InteractiveInterpreter = null;
    private GimpApp m_App = null;

    public static JGimpJythonInterpreter getInstance() {
        if (s_Instance == null) {
            s_Instance = new JGimpJythonInterpreter();
        }
        return s_Instance;
    }
    public JGimpJythonInterpreter() {
        /*
         * We are only be instantiated once by JGimp, so setting this static variable once should be fine
         */
        s_Instance = this;
    }
    
    /* 
     * @see org.gimp.jgimp.plugin.JGimpPlugIn#getPlugInInfo(org.gimp.jgimp.GimpApp)
     */
    public JGimpProcedureDescriptor[] getPlugInInfo(GimpApp app) {
        // We load up Jython scripts at this stage, but don't return any other information here
        loadJython(app);
        return null;
    }

    /* 
     * @see org.gimp.jgimp.plugin.JGimpPlugIn#remainResident(org.gimp.jgimp.GimpApp)
     */
    public boolean remainResident(GimpApp app) {
        // Shouldn't matter since we will hang around as long as the interpreter does
        return false;
    }

    /* (non-Javadoc)
     * @see org.gimp.jgimp.plugin.JGimpPlugIn#run(org.gimp.jgimp.GimpApp, java.lang.String, org.gimp.jgimp.JGimpData[])
     */
    public JGimpPlugInReturnValues run(GimpApp app, String inName, JGimpData[] inParams) {
        // Shouldn't ever be run
        return JGimpPlugInReturnValues.returnCallingError();
    }

    /* 
     * @see org.gimp.jgimp.extension.JGimpExtension#run(org.gimp.jgimp.GimpApp)
     */
    public void run(GimpApp app) {
        loadJython(app);
    }

    private void loadJython(GimpApp app) {
        m_App = app;
        String jgimpBaseDir = null;
        String systemScriptDirectory = null;
        String pluginScriptDirectory = null;
        String extensionScriptDirectory = null;
        String javafilesDirectory = null;
        String FILE_SEPARATOR = System.getProperty("file.separator");
 
        try {
            jgimpBaseDir = m_App.getResourceFileData(JGimpProxy.JGIMP_BASE_DIR_KEY);
            systemScriptDirectory = new File(jgimpBaseDir + FILE_SEPARATOR + "jython" + FILE_SEPARATOR + "system").getCanonicalPath().toString();
        } catch (Exception e) {
            JOptionPane.showMessageDialog(null, "Internal error: invalid Jython system script directory specified: " + systemScriptDirectory + ".", "JGimp Internal Error", JOptionPane.ERROR_MESSAGE);
            return;
        }

        try {
            pluginScriptDirectory = m_App.getResourceFileData(JYTHON_PLUGIN_DIR_KEY);
            if ((pluginScriptDirectory == null) || (pluginScriptDirectory.length() < 1)) {
                pluginScriptDirectory = new File(jgimpBaseDir + FILE_SEPARATOR + "jython" + FILE_SEPARATOR + "plugins").getCanonicalPath().toString();
            }
            extensionScriptDirectory = m_App.getResourceFileData(JYTHON_EXTENSION_DIR_KEY);
            if ((extensionScriptDirectory == null) || (extensionScriptDirectory.length() < 1)) {
                extensionScriptDirectory = new File(jgimpBaseDir + FILE_SEPARATOR + "jython" + FILE_SEPARATOR + "extensions").getCanonicalPath().toString();
            }
        } catch (Exception e) {
System.err.println("Caught exception setting plugin and extensions directories: " + e.getMessage()); // ignored
e.printStackTrace();
        }

        try {
            javafilesDirectory = m_App.getResourceFileData(JGimpProxy.JAVAFILES_DIR_KEY);
            if ((javafilesDirectory == null) || (javafilesDirectory.length() < 1)) {
                javafilesDirectory = new File(jgimpBaseDir + FILE_SEPARATOR + "javafiles").getCanonicalPath().toString();                
            }
        } catch (Exception e) {
            System.err.println("Internal error: invalid Java files directory specified: " + javafilesDirectory + ".");
            return;
        }
        
       /*
        * OK, the following 7 lines of code constitute a whole day's worth of
        * debugging... Since this code is an extension and loaded by a custom
        * class loader, Jython needs to know about that class loader, or it
        * will try to load stuff from the System loader. However, since all
        * other extensions/plug-ins are in the custom loader, including this
        * code, Jython will simply crash if it uses the System loader.
        * 
        * So we make a copy of the system properties, add all the .jar files,
        * add the javafiles class path to the class path of our copied properties,
        * pass in our own class loader, explicitly set it again, and then
        * everything works fine. sheesh.
        */
        Properties systemProperties = new Properties(System.getProperties());
        addPathsToProperties(systemProperties, new File(javafilesDirectory), null);
        addFileToPropertyClassPath(systemProperties, new File(javafilesDirectory));
        PySystemState.initialize(systemProperties, null, new String[] {""}, JGimpJythonInterpreter.class.getClassLoader());
    
        PySystemState pss = new PySystemState();
        pss.setClassLoader(JGimpJythonInterpreter.class.getClassLoader());
        m_Interpreter = new PythonInterpreter(null, pss);
       
        m_Interpreter.set("gimpApp", m_App);
        m_Interpreter.set("jgimpJythonInterpreter", this);
        m_Interpreter.exec("def importPlugInNames(): jgimpJythonInterpreter.importPlugInNames()");
        m_Interpreter.exec("import sys");
        m_Interpreter.exec("sys.path.append(r\"" + systemScriptDirectory + "\")");
        m_Interpreter.exec("sys.path.append(r\"" + systemScriptDirectory + "/Lib\")");
        m_Interpreter.exec("sys.path.append(r\"" + systemScriptDirectory + "/Tools\")");
        m_Interpreter.exec("import jgimp_core");
        
        boolean loadedPluginsDirectory = false;
        if (m_App.getProxy().canInstallPlugIns()) {
            if ((pluginScriptDirectory != null) && (pluginScriptDirectory.length() > 0)) {
                m_Interpreter.exec("sys.path.append(r\"" + pluginScriptDirectory + "\")");
                m_Interpreter.exec("jgimp_core.load_script_directory(r\"" + pluginScriptDirectory + "\")");
                loadedPluginsDirectory = true;
            }
        }
    
        if (m_App.getProxy().canLoadExtensions()) {
            if (extensionScriptDirectory != null) {
                if (!(loadedPluginsDirectory && (extensionScriptDirectory.equals(pluginScriptDirectory)))) {
                    m_Interpreter.exec("sys.path.append(r\"" + extensionScriptDirectory + "\")");
                    m_Interpreter.exec("jgimp_core.load_script_directory(r\"" + extensionScriptDirectory + "\")");
                }
            }
            if (m_InteractiveInterpreter == null) {
                m_InteractiveInterpreter = new InteractiveJythonInterpreter(this, m_App);
                m_InteractiveInterpreter.run();
            }
        }
    }
    
    private void addPathsToProperties(Properties properties, File curDirectory, Vector visitedDirectories) {
        if (visitedDirectories == null) {
            visitedDirectories = new Vector();
        }
        if (visitedDirectories.contains(curDirectory)) {
            return;
        }
        visitedDirectories.add(curDirectory);
        if (!curDirectory.isDirectory()) {
            return;
        }
        File[] files = curDirectory.listFiles();
        for (int i = 0; i < files.length; i++) {
            File thisFile = files[i];
            if (thisFile.isDirectory()) {
                addPathsToProperties(properties, thisFile, visitedDirectories);
            }
            else if (thisFile.getName().toLowerCase().endsWith(".jar") || thisFile.getName().toLowerCase().endsWith(".zip")) {
                addFileToPropertyClassPath(properties, thisFile);
            }
        }
    }
    
    private void addFileToPropertyClassPath(Properties properties, File file) {
        try {
            String curPath = properties.getProperty("java.class.path");
            curPath = curPath + properties.getProperty("path.separator") + file.getCanonicalPath().toString();
            properties.setProperty("java.class.path", curPath);                   
        } catch(Exception e) {
            System.err.println("Error adding file " + file.getName() + " to JythonInterpreter's system state");
            e.printStackTrace();
        }
    }

    /* 
     * @see org.gimp.jgimp.extension.JGimpExtension#stop(org.gimp.jgimp.GimpApp)
     */
    public void stop(GimpApp app) {
        // TODO Auto-generated method stub
        ; // no-op, but should probably stop any Jython stuff...
    }

    public GimpApp getGimpApp() {
        return m_App;
    }
    public void reload() {
    }
        
    public void importPlugInNames() {
        try {
            JGimpProcedureDescriptor[] procs = m_App.getAllProcedureDescriptions(".*", ".*", ".*", ".*", ".*", ".*", ".*");
            for (int i = 0; i < procs.length; i++) {
                JGimpProcedureDescriptor thisProc = procs[i];
                String procName = thisProc.getName().replace('-', '_');
                JGimpParamDescriptor[] params = thisProc.getParameterDescriptions();
                JGimpParamDescriptor[] returnVals = thisProc.getReturnValueDescriptions();
                String paramList = "";
                for (int j = 0; j < params.length; j++) {
                    String paramName = params[j].getName();
                    paramName = Pattern.compile("[^a-zA-Z]").matcher(paramName).replaceAll("");
                    if (paramName.length() < 1) {
                        paramName = "arg";
                    }
                    if (j > 0) {
                        paramList = paramList + ", ";
                    }
                    paramList = paramList + paramName + j;
                }
                String functionDefinition = "def " + procName + "(" + paramList + "):\n\t";
                if (returnVals.length > 0) {
                    functionDefinition = functionDefinition + "return ";
                }
                functionDefinition = functionDefinition + "gimpApp.callProcedure(\"" + procName + "\", [" + paramList + "])";
                if (returnVals.length == 1) {
                    functionDefinition = functionDefinition + "[0]";
                }
                functionDefinition = functionDefinition + "\n";
                this.getInterpreter().exec(functionDefinition);
            }
        } catch (Exception e) {
            System.err.println("Error importing function definition into Jython: " + e.getMessage() + " : " + e.toString());
        }
    }
    
    public PythonInterpreter getInterpreter() {
        return m_Interpreter;
    }
    
}
