/*
 * Copyright (c) 2009 Sun Microsystems, Inc. All Rights Reserved.
 * Copyright (c) 2010 JogAmp Community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * - Redistribution of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed or intended for use
 * in the design, construction, operation or maintenance of any nuclear
 * facility.
 */

package com.jogamp.newt.util;

import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

import com.jogamp.nativewindow.NativeWindowFactory;

import com.jogamp.common.os.Platform;
import com.jogamp.common.util.InterruptSource;
import com.jogamp.common.util.PropertyAccess;
import com.jogamp.common.util.ReflectionUtil;

import jogamp.newt.Debug;
import jogamp.newt.NEWTJNILibLoader;

/**
 * NEWT Utility class MainThread<P>
 *
 * <p>
 * FIXME: Update this documentation!
 * This class just provides a main-thread utility, forking of a main java class
 * on another thread while being able to continue doing platform specific things
 * on the main-thread. The latter is essential for eg. MacOSX, where we continue
 * to run NSApp.run().
 * </p>
 *
 * This class provides a startup singleton <i>main thread</i>,
 * from which a new thread with the users main class is launched.<br>
 *
 * Such behavior is necessary for native windowing toolkits,
 * where the windowing management must happen on the so called
 * <i>main thread</i> e.g. for Mac OS X !<br>
 *
 * Utilizing this class as a launchpad, now you are able to
 * use a NEWT multithreaded application with window handling within the different threads,
 * even on these restricted platforms.<br>
 *
 * To support your NEWT Window platform,
 * you have to pass your <i>main thread</i> actions to {@link #invoke invoke(..)},
 * have a look at the {@link jogamp.newt.driver.macosx.WindowDriver NEWT Mac OSX Window} driver implementation.<br>
 * <i>TODO</i>: Some hardcoded dependencies exist in this implementation,
 * where you have to patch this code or factor it out. <P>
 *
 * If your platform is not Mac OS X, but you want to test your code without modifying
 * this class, you have to set the system property <code>newt.MainThread.force</code> to <code>true</code>.<P>
 *
 * The code is compatible with all other platform, which support multithreaded windowing handling.
 * Since those platforms won't trigger the <i>main thread</i> serialization, the main method
 * will be simply executed, in case you haven't set <code>newt.MainThread.force</code> to <code>true</code>.<P>
 *
 * Test case on Mac OS X (or any other platform):
 <PRE>
    java -XstartOnFirstThread com.jogamp.newt.util.MainThread demos.es1.RedSquare -GL2 -GL2 -GL2 -GL2
 </PRE>
 * Which starts 4 threads, each with a window and OpenGL rendering.<br>
 */
public class MainThread {
    private static final String MACOSXDisplayClassName = "jogamp.newt.driver.macosx.DisplayDriver";
    private static final Platform.OSType osType;
    private static final boolean isMacOSX;
    private static final ThreadGroup rootThreadGroup;

    /** if true, use the main thread EDT, otherwise AWT's EDT */
    public static final boolean  HINT_USE_MAIN_THREAD;

    static {
        NativeWindowFactory.initSingleton();
        NEWTJNILibLoader.loadNEWTHead();
        HINT_USE_MAIN_THREAD = !NativeWindowFactory.isAWTAvailable() ||
                                PropertyAccess.getBooleanProperty("newt.MainThread.force", true);
        osType = Platform.getOSType();
        isMacOSX = osType == Platform.OSType.MACOS;
        rootThreadGroup = getRootThreadGroup();
    }

    public static boolean useMainThread = false;

    protected static final boolean DEBUG = Debug.debug("MainThread");

    private static final MainThread singletonMainThread = new MainThread(); // one singleton MainThread

    private static final ThreadGroup getRootThreadGroup() {
        ThreadGroup rootGroup = Thread.currentThread( ).getThreadGroup( );
        ThreadGroup parentGroup;
        while ( ( parentGroup = rootGroup.getParent() ) != null ) {
            rootGroup = parentGroup;
        }
        return rootGroup;
    }

    private static final Thread[] getAllThreads(final int[] count) {
        int tn;
        Thread[] threads = new Thread[ rootThreadGroup.activeCount() ];
        while ( ( tn = rootThreadGroup.enumerate( threads, true ) ) == threads.length ) {
            threads = new Thread[ threads.length * 2 ];
        }
        count[0] = tn;
        return threads;
    }
    private static final List<Thread> getNonDaemonThreads() {
        final List<Thread> res = new ArrayList<Thread>();
        final int[] tn = { 0 };
        final Thread[] threads = getAllThreads(tn);
        for(int i = tn[0] - 1; i >= 0; i--) {
            final Thread thread = threads[i];
            try {
                if(thread.isAlive() && !thread.isDaemon()) {
                    res.add(thread);
                    if(DEBUG) System.err.println("XXX0: "+thread.getName()+", "+thread);
                }
            } catch (final Throwable t) {
                t.printStackTrace();
            }
        }
        return res;
    }
    private static final int getNonDaemonThreadCount(final List<Thread> ignoreThreads) {
        int res = 0;
        final int[] tn = { 0 };
        final Thread[] threads = getAllThreads(tn);

        for(int i = tn[0] - 1; i >= 0; i--) {
            final Thread thread = threads[i];
            try {
                if(thread.isAlive() && !thread.isDaemon() && !ignoreThreads.contains(thread)) {
                    res++;
                    if(DEBUG) System.err.println("MainAction.run(): non daemon thread: "+thread);
                }
            } catch (final Throwable t) {
                t.printStackTrace();
            }
        }
        return res;
    }

    static class UserApp extends InterruptSource.Thread {
        private final String mainClassNameShort;
        private final String mainClassName;
        private final String[] mainClassArgs;
        private final Method mainClassMain;
        private List<java.lang.Thread> nonDaemonThreadsAtStart;

        public UserApp(final String mainClassName, final String[] mainClassArgs) throws SecurityException, NoSuchMethodException, ClassNotFoundException {
            this.mainClassName=mainClassName;
            this.mainClassArgs=mainClassArgs;

            final Class<?> mainClass = ReflectionUtil.getClass(mainClassName, true, getClass().getClassLoader());
            if(null==mainClass) {
                throw new ClassNotFoundException("MainAction couldn't find main class "+mainClassName);
            }
            mainClassNameShort = mainClass.getSimpleName();
            mainClassMain = mainClass.getDeclaredMethod("main", new Class[] { String[].class });
            mainClassMain.setAccessible(true);

            setName(getName()+"-UserApp-"+mainClassNameShort);
            setDaemon(false);

            if(DEBUG) System.err.println("MainAction(): instantiated: "+getName()+", is daemon "+isDaemon()+", main-class: "+mainClass.getName());
        }

        @Override
        public void run() {
            nonDaemonThreadsAtStart = getNonDaemonThreads();
            if(DEBUG) System.err.println("MainAction.run(): "+java.lang.Thread.currentThread().getName()+" start, nonDaemonThreadsAtStart "+nonDaemonThreadsAtStart);
            // start user app ..
            try {
                if(DEBUG) System.err.println("MainAction.run(): "+java.lang.Thread.currentThread().getName()+" invoke "+mainClassName);
                mainClassMain.invoke(null, new Object[] { mainClassArgs } );
            } catch (final InvocationTargetException ite) {
                ite.getTargetException().printStackTrace();
                return;
            } catch (final Throwable t) {
                t.printStackTrace();
                return;
            }

            // wait until no more active non-daemon threads are running
            {
                int ndtr;
                while( 0 < ( ndtr = getNonDaemonThreadCount(nonDaemonThreadsAtStart) ) ) {
                    if(DEBUG) System.err.println("MainAction.run(): post user app, non daemon threads alive: "+ndtr);
                    try {
                        java.lang.Thread.sleep(1000);
                    } catch (final InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if(DEBUG) System.err.println("MainAction.run(): "+java.lang.Thread.currentThread().getName()+" user app fin: "+ndtr);
            }

            if ( useMainThread ) {
                if(isMacOSX) {
                    try {
                        if(DEBUG) {
                            System.err.println("MainAction.main(): "+java.lang.Thread.currentThread()+" MainAction fin - stopNSApp.0");
                        }
                        ReflectionUtil.callStaticMethod(MACOSXDisplayClassName, "stopNSApplication",
                            null, null, MainThread.class.getClassLoader());
                        if(DEBUG) {
                            System.err.println("MainAction.main(): "+java.lang.Thread.currentThread()+" MainAction fin - stopNSApp.X");
                        }
                    } catch (final Exception e) {
                        e.printStackTrace();
                    }
                } else {
                    if(DEBUG) System.err.println("MainAction.run(): "+java.lang.Thread.currentThread().getName()+" MainAction fin - System.exit(0)");
                    System.exit(0);
                }
            }
        }
    }
    private static UserApp mainAction;

    /** Your new java application main entry, which pipelines your application
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws SecurityException */
    public static void main(final String[] args) throws SecurityException, NoSuchMethodException, ClassNotFoundException {
        final Thread cur = Thread.currentThread();

        useMainThread = HINT_USE_MAIN_THREAD;

        if(DEBUG) {
            System.err.println("MainThread.main(): "+cur.getName()+
                ", useMainThread "+ useMainThread +
                ", HINT_USE_MAIN_THREAD "+ HINT_USE_MAIN_THREAD +
                ", isAWTAvailable " + NativeWindowFactory.isAWTAvailable() + ", ostype "+osType+", isMacOSX "+isMacOSX);
        }

        if(!useMainThread && !NativeWindowFactory.isAWTAvailable()) {
            throw new RuntimeException("!USE_MAIN_THREAD and no AWT available");
        }

        if(args.length==0) {
            return;
        }

        final String mainClassName=args[0];
        final String[] mainClassArgs=new String[args.length-1];
        if(args.length>1) {
            System.arraycopy(args, 1, mainClassArgs, 0, args.length-1);
        }

        mainAction = new UserApp(mainClassName, mainClassArgs);

        if(isMacOSX) {
            ReflectionUtil.callStaticMethod(MACOSXDisplayClassName, "initSingleton",
                null, null, MainThread.class.getClassLoader());
        }

        if ( useMainThread ) {
            try {
                cur.setName(cur.getName()+"-MainThread");
            } catch (final Exception e) {}

            // dispatch user's main thread ..
            mainAction.start();

            if(isMacOSX) {
                try {
                    if(DEBUG) {
                        System.err.println("MainThread.main(): "+cur.getName()+"- runNSApp");
                    }
                    ReflectionUtil.callStaticMethod(MACOSXDisplayClassName, "runNSApplication",
                        null, null, MainThread.class.getClassLoader());
                } catch (final Exception e) {
                    e.printStackTrace();
                }
            }
            if(DEBUG) { System.err.println("MainThread - wait until last non daemon thread ends ..."); }
        } else {
            // run user's main in this thread
            mainAction.run();
        }
    }

    public static MainThread getSingleton() {
        return singletonMainThread;
    }

}


