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

import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import net.ranides.assira.annotations.Meta;
import net.ranides.assira.awt.AWTInvoker;
import net.ranides.assira.reflection.*;
import net.ranides.assira.reflection.MethodFactory.MethodPointer;
import net.ranides.assira.reflection.inspect.MethodInspector;
import net.ranides.assira.reflection.inspect.MethodInspector.MethodFilter;
import net.ranides.assira.reflection.util.MethodUtils;
import net.ranides.assira.trace.AdvLogger;
import net.ranides.assira.trace.LoggerUtils;

/**
 * Obserwator pozwalający tworzyć klasy pochodne, które bezpośrednio w deklaracji
 * klasy definiują odpowiednie wiązania z nadchodzącymi zdarzeniami. Każde zdarzenie
 * otrzymane przez obserwatora jest przekazywane do wszystkich metod spełniających
 * kryteria. Deklaracja metody obsługi zdarzeń ma następującą postać:
 * <ul>
 *      <li>metoda pobiera tylko jeden argument</li>
 *      <li>typ argumentu odpowiada typowi obserwowanego zdarzenia</li>
 *      <li>metoda jest adnotowana za pomocą {@link net.ranides.assira.annotations.Meta.EventHandler}</li>
 *      <li>{@link net.ranides.assira.annotations.Meta.EventHandler} opcjonalnie określa sposób synchronizacji metody względem innych wątków</li>
 * </ul>
 * <p>
 * Jeśli chcesz wiązać zdarzenia na podstawie typu <b>oraz nazwy</b>, zobacz klasę {@link NamedEventListener}.
 * </p>
 *
 * <div class="message-note">thread-safe class</div>
 *
 * @param <T>
 * @author ranides
 */
public class ReflectEventListener<T extends Event> implements EventListener<T> {

    private static final AdvLogger LOGGER = LoggerUtils.getLogger();

    private static final Class[] SUPPORTED_ARGS = { Event.class };

    private static final Class[] EXCEPTION_ARGS = { Throwable.class };

    private final List<EventListener<Event>> listeners = new LinkedList<>();

    private final List<GenericMethod<?>> errorHandlers = new LinkedList<>();

    /**
     * Tworzy nowy obserwator, który przekazuje wszystkie zdarzenia do podanego
     * obiektu, wywołując na jego rzecz odpowiednie metody dopasowane na podstawie
     * typu zdarzenia. Przeczytaj opis klasy, aby poznać wymagania wobec metod obsługi zdarzeń.
     * @param delegator
     */
    public ReflectEventListener(Object delegator) {
        init(delegator);
    }

    /**
     * {@code ReflectEventListener} nie może tworzony bez pośrednio - można jedynie
     * tworzyć jego klasy pochodne. Po otrzymaniu zdarzenia uruchamia przy użyciu
     * refleksji odpowiednie metody obsługi zdarzeń. Przeczytaj opis klasy,
     * aby poznać wymagania wobec metod obsługi zdarzeń.
     */
    protected ReflectEventListener() {
        init(this);
    }

    private void init(Object delegator) {
        MethodFilter filter1 = MethodInspector.inobject(delegator)
            .follow().declared()
            .params(SUPPORTED_ARGS)
            .contravariant()
            .annotated(Meta.EventHandler.class);
        for(MethodPointer<?> pointer : filter1.pointers()) {
            listeners.add(wrap(delegator, pointer.reflective()));
        }
        MethodFilter filter2 = MethodInspector.inobject(delegator)
            .follow().declared()
            .params(EXCEPTION_ARGS)
            .contravariant()
            .annotated(Meta.ErrorHandler.class);
        for(MethodPointer<?> pointer : filter2.pointers()) {
            errorHandlers.add( pointer.bind(delegator) );
        }
    }

    @Override
    public final void handleEvent(T event) {
        for(EventListener<Event> listener : listeners) {
            listener.handleEvent(event);
        }
    }

/* ************************************************************************** */

    /**
     * Klasa do użycia wewnętrznego. Reprezentuje obserwatora, który po otrzymaniu
     * zdarzenia o podanej klasie wywoła określoną metodę na rzecz obiektu przekazanego
     * konstruktorowi. Adapter filtruje typ zdarzenia oraz dba o synchronizację
     * wywołania metody.
     * <p>
     * Obiekty typu {@code ReflectAdapter} nie mogą być tworzone bezpośrednio.
     * Do ich utworzenia należy użyj jednej z metod statycznych {@link #wrap}.
     */
    private final class ReflectAdapter implements EventListener<Event> {
        private final Class<?> type;
        private final boolean awt;
        private final boolean sync;
        private final Object object;
        private final Method method;

        ReflectAdapter(Class<?> type, Object object, Method method) {
            this.type   = type;
            this.object = object;
            this.method = method;
            this.awt    = false;
            this.sync   = false;
        }

        ReflectAdapter(Class<?> type, Object object, Method method, boolean sync) {
            this.type   = type;
            this.object = object;
            this.method = method;
            this.awt    = true;
            this.sync   = sync;
        }

        @Override
        public void handleEvent(Event event) {
            if( !type.isInstance(event) ) {
                return;
            }
            if(awt) {
                AWTInvoker.run(sync, object, method, event);
                return;
            }
            try {
                MethodUtils.invoke(object, method, event);
            } catch(InvokeException incause) {
                Throwable cause = incause.getCause();
                for(GenericMethod<?> pointer : errorHandlers) {
                    if(pointer.canCall(cause)) {
                        try {
                            pointer.call(cause);
                        } catch(Exception fatal) {
                            LOGGER.xfatal("Unexpected exception caused by: %s\n\tThread: %s\n\tObject: %s\n\tMethod: #%s\n\tEvent: %s\n\tHandler: %s", fatal, cause, Thread.currentThread().getName(), object, method.getName(), event, pointer);
                        }
                    }
                }
            } catch(InspectException cause) {
                LOGGER.xerror("Internal event dispath error.\n\tThread: %s\n\tObject: %s\n\tMethod: #%s\n\tEvent: %s", cause, Thread.currentThread().getName(), object, method.getName(), event);
            }
        }

    };

    /**
     * Tworzy nowy obserwator-adapter, który po otrzymaniu zdarzenia wywołuję
     * podaną metodę na rzecz podanego obiektu. Jeśli typ zdarzenia nie odpowiada
     * podanej klasie, obserwator nie wykonuje żadnej akcji.
     * @param clazz
     * @param object
     * @param handler
     * @return
     */
    private EventListener<Event> wrap(Object object, Method handler) {
        Meta.EventHandler info = handler.getAnnotation(Meta.EventHandler.class);
        Class<?> type = handler.getParameterTypes()[0];

        switch(info.sync()) {
            case AWT:
                return new ReflectAdapter(type, object, handler, false);
            case AWT_WAIT:
                return new ReflectAdapter(type, object, handler, true);
            case NONE:
                return new ReflectAdapter(type, object, handler);
            default:
                throw new UnsupportedOperationException(info.sync() + " is not supported");
        }
    }

}
