/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.awt;

import java.applet.Applet;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.Method;
import javax.swing.SwingUtilities;
import net.ranides.assira.collection.ArrayUtils;
import net.ranides.assira.events.EventListener;
import net.ranides.assira.generic.Function;
import net.ranides.assira.reflection.util.MethodUtils;
import net.ranides.assira.trace.AdvLogger;
import net.ranides.assira.trace.LoggerUtils;

/**
 * Klasa pomocnicza, oferująca większą funkcjonalność niż klasyczne metody ze
 * {@link SwingUtilities}. 
 * 
 * <p><b>Obsługa wyjątków przez metody {@code invokeLater}:</b></p>
 * <p>
 * Klasa przechwytuje wszystkie wyjątki i loguje za pomocą log4j. Zasadniczo
 * domyślna reakcja AWT jest bardzo podobna, a nawet gorsza: pisze na stdout. 
 * Jedynie wyjątki typu {@code InterruptedException} są przetwarzane odmiennie:
 * po zalogowaniu są wyrzucane na zewnątrz, mimo, że metody nie deklarują tego
 * wyjątku). Gdyby takie działanie nie miało miejsca, istniałoby niebezpieczeństwo
 * utworzenia wątku niemożliwego do zatrzymania za pomocą {@link Thread#interrupt}.
 * </p>
 * 
 * @author ranides
 */
public final class AWTInvoker {
    
    private static final AdvLogger LOGGER = LoggerUtils.getLogger();
    
    private AWTInvoker() {
        // utility class
    }

    /**
     * Wykonuje przekazaną operację w wątku AWT. 
     * Jeśli {@code sync=true}, to czeka na zakończenie operacji.
     * @param sync
     * @param runnable 
     * @return {@code false} jeśli wystąpił błąd (w szczególności InterruptedException)
     */
    public static boolean run(boolean sync, Runnable runnable) {
        try {
            if(sync) {
                if( EventQueue.isDispatchThread() ) {
                    runnable.run();
                } else {
                    SwingUtilities.invokeAndWait(runnable);
                }
            } else {
                SwingUtilities.invokeLater(runnable);
            }
            return true;
        } catch(InterruptedException cause) {
            LOGGER.xerror("Thread interrupted: %s. Action: %s", cause, thread(), runnable);
            return false;
        } catch (Exception cause) {
            LOGGER.xerror("Unexpected exception in thread: %s. Action: %s", cause, thread(), runnable);
            return false;
        }
    }
    
    private static String thread() {
        return Thread.currentThread().getName();
    }
    
    /**
     * Wykonuje przekazaną funkcję w wątku AWT z podanym argumentem.
     * Jeśli {@code sync=true}, to czeka na zakończenie funkcji.
     * @param <T>
     * @param sync
     * @param function
     * @param argument
     * @return {@code false} jeśli wystąpił błąd (w szczególności InterruptedException)
     */
    public static <T> boolean run(boolean sync, final Function<?, ? super T> function, final T argument) {
        return run(sync, new Runnable() {
            @Override
            public void run() { function.apply(argument);  }
            @Override
            public String toString() {
                return String.format("[native] Function: %s Argument: %s", function, argument);
            }
        });
    }
    
    /**
     * Wywołuje obsługę podanego zdarzenia w wątku AWT.
     * Jeśli {@code sync=true}, to czeka na zakończenie funkcji.
     * @param <T>
     * @param sync
     * @param listener
     * @param event
     * @return {@code false} jeśli wystąpił błąd (w szczególności InterruptedException)
     */
    public static <T> boolean run(boolean sync, final EventListener<? super T> listener, final T event) {
        return run(sync, new Runnable() {
            @Override
            public void run() { listener.handleEvent(event); }
            @Override
            public String toString() {
                return String.format("[native] Listener: %s Event: %s", listener, event);
            }
        });
    }
    
    /**
     * Wykonuje w wątku AWT przekazaną metodę z podanymi argumentami, na rzecz 
     * przekazanego obiektu za pomocą reflection API. 
     * Jeśli {@code sync=true}, to czeka na zakończenie funkcji.
     * @param <T>
     * @param sync
     * @param object
     * @param method
     * @param arguments
     * @return {@code false} jeśli wystąpił błąd (w szczególności InterruptedException)
     */
    public static <T> boolean run(boolean sync, final Object object, final Method method, final Object... arguments) {
        return run(sync, new Runnable() {
            @Override
            public void run() { MethodUtils.invoke(object, method, arguments); }
            @Override
            public String toString() {
                return String.format("[native] Object: %s Method: #%s Arguments: %s", object, method.getName(), ArrayUtils.wrap(arguments));
            }
        });
    }
    
    /**
     * Metoda pomocnicza, otwiera nowe okno {@link Window} wewnątrz którego jest
     * umieszczony podany aplet.
     * @param applet
     * @param title
     * @param width
     * @param height 
     * @return okno, zawierające zagnieżdżony i uruchomiony aplet
     */
    public static Window runApplet(final Applet applet, final String title, final int width, final int height) {
        final Frame frame = new Frame(title);
        // allow some extra room for the frame title bar.
        frame.setSize(width + 16, height + 36);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent event) {
                applet.stop();
                applet.destroy();
                System.exit(0);
            }
        });
        frame.add(applet);
        applet.init();
        frame.validate();
        frame.setVisible(true);
        applet.start();
        return frame;
    }
    
}
