package org.gudy.azureus2.platform.macosx.access.cocoa;

/*
 * Created on 27-Mar-2005
 * Created by James Yeh
 * Copyright (C) 2004-2005 Aelitis, All Rights Reserved.
 *
 * 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.
 *
 * AELITIS, SARL au capital de 30,000 euros
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 *
 */

import com.apple.cocoa.foundation.NSAppleEventDescriptor;
import com.apple.cocoa.foundation.NSAppleScript;
import com.apple.cocoa.foundation.NSAutoreleasePool;
import com.apple.cocoa.foundation.NSMutableDictionary;
import org.gudy.azureus2.core3.logging.LGLogger;
import org.gudy.azureus2.core3.util.AEMonitor;
import org.gudy.azureus2.core3.util.AERunnable;
import org.gudy.azureus2.core3.util.AEThread;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.platform.macosx.NativeInvocationBridge;

import java.io.File;
import java.text.MessageFormat;

/**
 * <p>Performs PlatformManager tasks using Cocoa-Java (FoundationKit only)</p>
 * <p>For now, operations are performed using NSAppleScript, rather than using NSWorkspace.
 * This is still significantly faster than calling the cmd-line osascript.</p>
 * @version 2.1 Apr 2, 2005
 */
public final class CocoaJavaBridge extends NativeInvocationBridge
{
    /**
     * The path the Cocoa-Java class files are located at
     */
    protected static final String CLASS_PATH = "/system/library/java";

    private static final String PF_SCRIPT_SRC = "tell application \"System Events\" to exists process \"Path Finder\"";
    private final NSAppleScript PF_SCRIPT;
    private final boolean USE_PF;

    private static final String REVEAL_SCRIPT_FORMAT = "tell application \"System Events\"\ntell application \"{0}\"\nactivate\nreveal (posix file \"{1}\" as alias)\nend tell\nend tell";

    private static final String DEL_SCRIPT_FORMAT = "tell application \"Finder\" to move (posix file \"{0}\" as alias) to the trash";

    /**
     * Main NSAutoreleasePool
     */
    private int mainPool;

    protected AEMonitor classMon = new AEMonitor("CocoaJavaBridge:C");
    private AEMonitor scriptMon = new AEMonitor("CocoaJavaBridge:S");

    protected boolean isDisposed = false;

    protected RunnableDispatcher scriptDispatcher;

    public CocoaJavaBridge()
    {
        try
        {
            classMon.enter();
            mainPool = NSAutoreleasePool.push();

            PF_SCRIPT = new NSAppleScript(PF_SCRIPT_SRC);
            USE_PF = PF_SCRIPT.compile(new NSMutableDictionary());

            scriptDispatcher = new RunnableDispatcher();
        }
        finally
        {
            classMon.exit();
        }
    }

    // interface implementation

    /**
     * {@inheritDoc}
     */
    protected boolean performRecoverableFileDelete(File path)
    {
        if(!path.exists())
            return false;

        NSAppleEventDescriptor result =  executeScriptWithAsync(DEL_SCRIPT_FORMAT, new Object[]{path.getAbsolutePath()});
        return (result != null);
    }

    /**
     * {@inheritDoc}
     */
    protected boolean showInFinder(File path)
    {
        if(!path.exists())
            return false;

        int pool = NSAutoreleasePool.push();

        String fb = "Finder";
        if(USE_PF)
        {
            NSAppleEventDescriptor result = PF_SCRIPT.execute(new NSMutableDictionary());
            if(result != null && result.booleanValue())
                fb = "Path Finder";
        }

        NSAppleEventDescriptor result =  executeScriptWithAsync(REVEAL_SCRIPT_FORMAT, new Object[]{fb, path.getAbsolutePath()});

        NSAutoreleasePool.pop(pool);
        return (result != null);
    }

    /**
     * {@inheritDoc}
     */
    protected boolean isEnabled()
    {
        // simple check with classpath
        return System.getProperty("java.class.path").toLowerCase().indexOf(CLASS_PATH) != -1;
    }

    // class utility methods

    /**
     * <p>Executes a new instance of NSAppleScript</p>
     * <p>The method is wrapped in an autorelease pool and an AEMonitor. If there are
     * no format parameters, MessageFormat is not used to parse the format string, and
     * the format string will be treated as the source itself.</p>
     * @see MessageFormat#format(String, Object...)
     * @see NSAppleScript#execute(com.apple.cocoa.foundation.NSMutableDictionary)
     */
    protected final NSAppleEventDescriptor executeScript(String scriptFormat, Object[] params)
    {
        try
        {
            scriptMon.enter();

            int pool = NSAutoreleasePool.push();
            long start = System.currentTimeMillis();

            String src;
            if(params == null || params.length == 0)
            {
                src = scriptFormat;
            }
            else
            {
                src = MessageFormat.format(scriptFormat, params);
            }

            Debug.outNoStack("Executing: \n" + src);

            NSAppleScript scp = new NSAppleScript(src);
            NSAppleEventDescriptor result =  scp.execute(new NSMutableDictionary());

            Debug.outNoStack(MessageFormat.format("Elapsed time: {0}ms\n", new Object[]{new Long(System.currentTimeMillis() - start)}));
            NSAutoreleasePool.pop(pool);
            return result;
        }
        finally
        {
            scriptMon.exit();
        }
    }

    /**
     * <p>Executes a new instance of NSAppleScript in a forked AEThread</p>
     * <p>This method always returns a "true" event descriptor. Callbacks are currently unsupported
     * , so in the event of an error, the logger is autuomatically notified.</p>
     * <p>The thread's runSupport method is wrapped in an autorelease pool. If there are
     * no format parameters, MessageFormat is not used to parse the format string, and
     * the format string will be treated as the source itself.</p>
     * @see org.gudy.azureus2.core3.util.AEThread#runSupport()
     * @see MessageFormat#format(String, Object...)
     * @see NSAppleScript#execute(com.apple.cocoa.foundation.NSMutableDictionary)
     * @return NSAppleEventDescriptor.descriptorWithBoolean(true)
     */
    protected final NSAppleEventDescriptor executeScriptWithNewThread(final String scriptFormat, final Object[] params)
    {
        Thread worker = new AEThread("ScriptObject", true)
        {
            public void runSupport()
            {
                int pool = NSAutoreleasePool.push();
                long start = System.currentTimeMillis();

                String src;
                if(params == null || params.length == 0)
                {
                    src = scriptFormat;
                }
                else
                {
                    src = MessageFormat.format(scriptFormat, params);
                }

                Debug.outNoStack("Executing: \n" + src);

                NSMutableDictionary errorInfo = new NSMutableDictionary();
                if(new NSAppleScript(src).execute(errorInfo) == null)
                {
                    Debug.out(String.valueOf(errorInfo.objectForKey(NSAppleScript.AppleScriptErrorMessage)));
                    //logWarning(String.valueOf(errorInfo.objectForKey(NSAppleScript.AppleScriptErrorBriefMessage)));
                }

                Debug.outNoStack(MessageFormat.format("Elapsed time: {0}ms\n", new Object[]{new Long(System.currentTimeMillis() - start)}));
                NSAutoreleasePool.pop(pool);
            }
        };

        worker.setPriority(Thread.NORM_PRIORITY - 1);
        worker.start();

        return NSAppleEventDescriptor.descriptorWithBoolean(true);
    }

    /**
     * <p>Asynchronously executes a new instance of NSAppleScript</p>
     * <p>This method always returns a "true" event descriptor. Callbacks are currently unsupported
     * , so in the event of an error, the logger is autuomatically notified.</p>
     * <p>The thread's runSupport method is wrapped in an autorelease pool. If there are
     * no format parameters, MessageFormat is not used to parse the format string, and
     * the format string will be treated as the source itself.</p>
     * @see org.gudy.azureus2.core3.util.AEThread#runSupport()
     * @see MessageFormat#format(String, Object...)
     * @see NSAppleScript#execute(com.apple.cocoa.foundation.NSMutableDictionary)
     * @return NSAppleEventDescriptor.descriptorWithBoolean(true)
     */
    protected final NSAppleEventDescriptor executeScriptWithAsync(final String scriptFormat, final Object[] params)
    {
        final AERunnable worker = new AERunnable()
        {
            public void runSupport()
            {
                int pool = NSAutoreleasePool.push();
                long start = System.currentTimeMillis();

                String src;
                if(params == null || params.length == 0)
                {
                    src = scriptFormat;
                }
                else
                {
                    src = MessageFormat.format(scriptFormat, params);
                }

                Debug.outNoStack("Executing: \n" + src);

                NSMutableDictionary errorInfo = new NSMutableDictionary();
                if(new NSAppleScript(src).execute(errorInfo) == null)
                {
                    Debug.out(String.valueOf(errorInfo.objectForKey(NSAppleScript.AppleScriptErrorMessage)));
                    //logWarning(String.valueOf(errorInfo.objectForKey(NSAppleScript.AppleScriptErrorBriefMessage)));
                }

                Debug.outNoStack(MessageFormat.format("Elapsed time: {0}ms\n", new Object[]{new Long(System.currentTimeMillis() - start)}));
                NSAutoreleasePool.pop(pool);
            }
        };

        AEThread t = new AEThread("ScriptObject", true)
        {
            public void runSupport()
            {
                scriptDispatcher.exec(worker);
            }
        };
        t.setPriority(Thread.NORM_PRIORITY - 1);
        t.start();

        return NSAppleEventDescriptor.descriptorWithBoolean(true);
    }

    /**
     * Logs a warning message to LGLogger. The class monitor is used.
     * @param message A warning message
     */
    private void logWarning(String message)
    {
        try
        {
            classMon.enter();
            LGLogger.logUnrepeatableAlert(LGLogger.AT_WARNING, message);
        }
        finally
        {
            classMon.exit();
        }
    }

    // disposal

    /**
     * {@inheritDoc}
     */
    protected void dispose()
    {
        try
        {
            classMon.enter();
            if(!isDisposed)
            {
                Debug.outNoStack("Disposing Native PlatformManager...");
                NSAutoreleasePool.pop(mainPool);
                isDisposed = true;
                Debug.outNoStack("Done");
            }
        }
        finally
        {
            classMon.exit();
        }
    }

    /**
     * {@inheritDoc}
     */
    protected void finalize() throws Throwable
    {
        dispose();
        super.finalize();
    }

    /**
     * A dispatch object to help facilitate asychronous script execution (from the main thread) in a more
     * predictable fashion.
     */
    private static class RunnableDispatcher
    {
        /**
         * Executes a Runnable object while synchronizing the RunnableDispatcher instance.
         * @param runnable A Runnable
         */
        private void exec(Runnable runnable)
        {
            synchronized(this)
            {
                runnable.run();
            }
        }
    }
}
