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

import java.util.List;
import net.ranides.assira.awt.AWTInvoker;
import net.ranides.assira.functional.VarFunction;
import net.ranides.assira.reflection.IAttribute;
import net.ranides.assira.reflection.IClass;
import net.ranides.assira.reflection.IMethod;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class EventObserver implements EventListener<Event> {
    
    List<IHandler> listeners;

    public EventObserver() {
        listeners = init(this);
    }
    
    public EventObserver(Object delegator) {
        listeners = init(delegator);
    }

    private static List<IHandler> init(Object delegator) {
        return IClass.typefor(delegator)
            .methods()
            .require(EventHandler.class)
            .require(IAttribute.VOID)
            .matches(IClass.typeinfo(Event.class).collect())
            .list(m -> listener(delegator, m));
    }
    
    private static IHandler listener(Object delegator, IMethod m) {
        EventHandler info = m.annotations().first(EventHandler.class).get();
        IClass<?> type = m.arguments().first().get().type();
        String router = info.router();
        switch(router) {
            case "":
                return new THandler(type, m.bind(delegator));
            case EventHandler.UI:
                return new UIHandler(type, false, m.bind(delegator));
            case EventHandler.UI_WAIT:
                return new UIHandler(type, true, m.bind(delegator));
            default:
                return new NHandler(router, type, m.bind(delegator));
        }
        
    }

    @Override
    public void handleEvent(Event event) {
        for(IHandler handler : listeners) {
            if(handler.matches(event)) {
                handler.call(event);
            }
        }
    }
    
    private static abstract class IHandler {
        
        private final IClass<?> type;

        public IHandler(IClass<?> type) {
            this.type = type;
        }

        public boolean matches(Event event) {
            return type.isInstance(event);
        }
        
        public abstract void call(Event event);
        
    }
    
    private static final class THandler extends IHandler {
        
        private final VarFunction handle;

        public THandler(IClass<?> type, VarFunction handle) {
            super(type);
            this.handle = handle;
        }
        
        @Override
        public void call(Event event) {
            handle.call(event);
        }
        
    }
    
    private static final class UIHandler extends IHandler {
        
        private final boolean sync;
        
        private final VarFunction handle;

        public UIHandler(IClass<?> type, boolean sync, VarFunction handle) {
            super(type);
            this.handle = handle;
            this.sync = sync;
        }
        
        @Override
        public void call(Event event) {
            AWTInvoker.run(sync, () -> handle.call(event));
        }
        
    }
    
    private static final class NHandler extends IHandler {
        
        private final EventDispatcher dispatcher;
        
        public NHandler(String name, IClass<?> type, VarFunction handle) {
            super(type);
            this.dispatcher = EventDispatcher.find(name);
            this.dispatcher.addEventListener(DEvent.class, new DEventListener(this, handle));
        }
        
        @Override
        public void call(Event event) {
            this.dispatcher.signalEvent(new DEvent(this, event));
        }
        
    }
    
    private static final class DEventListener implements EventListener<DEvent> {
        
        private final Object tag;
        
        private final VarFunction handle;

        public DEventListener(Object tag, VarFunction handle) {
            this.tag = tag;
            this.handle = handle;
        }
        
        @Override
        public void handleEvent(DEvent event) {
            if(event.tag == tag) {
                handle.call(event.src);
            }
        }
        
    }
    
    private static final class DEvent implements Event {
    
        public final Object tag;
        
        public final Event src;

        public DEvent(Object tag, Event event) {
            this.tag = tag;
            this.src = event;
        }
        
    }
    
}
