package ij;

import ij.IJ;
import ij.ImageJ;
import ij.Prefs;

import ij.io.OpenDialog;
import ij.io.Opener;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import java.lang.reflect.Method;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

import java.util.Properties;

/*
 * This class tries to contact another instance on the same machine, started
 * by the current user.  If such an instance is found, the arguments are
 * sent to that instance.  If no such an instance is found, listen for clients.
 *
 * No need for extra security, as the stub (and its serialization) contain
 * a hard-to-guess hash code.
 *
 *  @author Johannes Schindelin
 */
public class OtherInstance {
    private static final String DELIMETER = "~!~"; // Separates macro name and argument

    interface ImageJInstance extends Remote {
        void sendArgument(String arg) throws RemoteException;
    }

    static class Implementation implements ImageJInstance {
        int counter = 0;

        public void sendArgument(String cmd) {
            if (IJ.debugMode) IJ.log("SocketServer.sendArgument: \""+ cmd+"\"");
            if (cmd.startsWith("open "))
                (new Opener()).openAndAddToRecent(cmd.substring(5));
            else if (cmd.startsWith("macro ")) {
                String name = cmd.substring(6);
                String name2 = name;
                String arg = null;
                int index = name2.indexOf(DELIMETER);
                if (index!=-1) {
                    name = name2.substring(0, index);
                    arg = name2.substring(index+DELIMETER.length(), name2.length());
                }
                IJ.runMacroFile(name, arg);
            } else if (cmd.startsWith("run "))
                IJ.run(cmd.substring(4));
            else if (cmd.startsWith("eval ")) {
                String rtn = IJ.runMacro(cmd.substring(5));
                if (rtn!=null)
                    System.out.print(rtn);
            } else if (cmd.startsWith("user.dir "))
                OpenDialog.setDefaultDirectory(cmd.substring(9));
        }
    }

    public static String getStubPath() {
        String display = System.getenv("DISPLAY");
        if (display!=null) {
            display = display.replace(':', '_');
            display = display.replace('/', '_');
        }
        String tmpDir = System.getProperty("java.io.tmpdir");
        if (!tmpDir.endsWith(File.separator))
            tmpDir = tmpDir + File.separator;

        return tmpDir + "ImageJ-"
            + System.getProperty("user.name") + "-"
            + (display == null ? "" : display + "-")
            + ImageJ.getPort() + ".stub";
    }

    public static void makeFilePrivate(String path) {
        try {
            File file = new File(path);
            file.deleteOnExit();

            // File.setReadable() is Java 6
            Class[] types = { boolean.class, boolean.class };
            Method m = File.class.getMethod("setReadable", types);
            Object[] arguments = { Boolean.FALSE, Boolean.FALSE };
            m.invoke(file, arguments);
            arguments = new Object[] { Boolean.TRUE, Boolean.TRUE };
            m.invoke(file, arguments);
            types = new Class[] { boolean.class };
            m = File.class.getMethod("setWritable", types);
            arguments = new Object[] { Boolean.FALSE };
            m.invoke(file, arguments);
            return;
        } catch (Exception e) {
            if (IJ.debugMode)
                System.err.println("Java < 6 detected,"
                    + " trying chmod 0600 " + path);
        }
        if (!IJ.isWindows()) {
            try {
                String[] command = {
                    "chmod", "0600", path
                };
                Runtime.getRuntime().exec(command);
            } catch (Exception e) {
                if (IJ.debugMode)
                    System.err.println("Even chmod failed.");
            }
        }
    }

    public static boolean sendArguments(String[] args) {
        if (!isRMIEnabled())
            return false;
        String file = getStubPath();
        try {
            FileInputStream in = new FileInputStream(file);
            ImageJInstance instance = (ImageJInstance) new ObjectInputStream(in).readObject();
            in.close();
            if (instance==null)
                return false;
            instance.sendArgument("user.dir "+System.getProperty("user.dir"));
            int macros = 0;
            for (int i=0; i<args.length; i++) {
                String arg = args[i];
                if (arg==null)
                    continue;
                String cmd = null;
                if (macros==0 && arg.endsWith(".ijm")) {
                    cmd = "macro " + arg;
                    macros++;
                } else if (arg.startsWith("-macro") && i+1<args.length) {
                    String macroArg = i+2<args.length?DELIMETER+args[i+2]:"";
                    cmd = "macro " + args[i+1] + macroArg;
                    instance.sendArgument(cmd);
                    break;
                } else if (arg.startsWith("-eval") && i+1<args.length) {
                    cmd = "eval " + args[i+1];
                    args[i+1] = null;
                } else if (arg.startsWith("-run") && i+1<args.length) {
                    cmd = "run " + args[i+1];
                    args[i+1] = null;
                } else if (arg.indexOf("ij.ImageJ")==-1 && !arg.startsWith("-"))
                    cmd = "open " + arg;
                if (cmd!=null)
                    instance.sendArgument(cmd);
            } // for
            return true;
        } catch (Exception e) {
            if (IJ.debugMode) {
                System.err.println("Client exception: " + e);
                e.printStackTrace();
            }
            new File(file).delete();
        }
        if (!new File(file).exists())
            startServer();
        //IJ.log("sendArguments: return false ");
        return false;
    }

    static ImageJInstance stub;
    static Implementation implementation;

    public static void startServer() {
        if (IJ.debugMode)
            System.err.println("OtherInstance: starting server");
        try {
            implementation = new Implementation();
            stub = (ImageJInstance)UnicastRemoteObject.exportObject(implementation, 0);

            // Write serialized object
            String path = getStubPath();
            FileOutputStream out = new FileOutputStream(path);
            makeFilePrivate(path);
            new ObjectOutputStream(out).writeObject(stub);
            out.close();

            if (IJ.debugMode)
                System.err.println("OtherInstance: server ready");
        } catch (Exception e) {
            if (IJ.debugMode) {
                System.err.println("Server exception: " + e);
                e.printStackTrace();
            }
        }
    }

    private static final String OPTIONS = "prefs.options";
    private static final int RUN_SOCKET_LISTENER=1<<22;

    public static boolean isRMIEnabled() {
        if (System.getProperty("os.name").startsWith("Mac"))
            return true;
        Properties ijProps = loadPrefs();
        if (ijProps==null)
            return true;
        int options = getInt(ijProps, OPTIONS);
        if (options==-1)
            return true;
        return (options&RUN_SOCKET_LISTENER)!=0;
    }
    
    protected static int getInt(Properties props, String key) {
        String s = props.getProperty(key);
        if (s!=null) {
            try {
                return Integer.decode(s).intValue();
            } catch (NumberFormatException e) {IJ.write(""+e);}
        }
        return -1;
    }

    protected static Properties loadPrefs() {
        Properties result = new Properties();
        File file = new File(getPrefsDirectory(), "IJ_Prefs.txt");
        try {
            InputStream in = new BufferedInputStream(new FileInputStream(file));
            result.load(in);
            in.close();
        } catch (IOException e) { /* ignore */ }
        return result;
    }

    protected static String getPrefsDirectory() {
        String env = System.getenv("IJ_PREFS_DIR");
        if (env != null)
            return env;
        else
            return Prefs.getPrefsDir();
    }
}