package com.jscape.util.event;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @author Alex Usun
 */
public class CommonEventContext
        implements EventContext {

    private final String name;
    private EventContext enclosedContext;
    private final Registry registry;
    private final Map<Object, Object> attributes;

    /**
     * @param name            name
     * @param enclosedContext enclosedContext
     */
    public CommonEventContext(String name, EventContext enclosedContext) {
        this.name = name;
        this.enclosedContext = enclosedContext;
        this.registry = new Registry();
        this.attributes = new HashMap<Object, Object>();
    }

    /**
     * @param name name
     */
    public CommonEventContext(String name) {
        this(name, null);
    }

    /** */
    public CommonEventContext() {
        this("", null);
    }

    /**
     * @return name
     */
    public String getName() {
        return this.name;
    }

    /**
     * @return enclosedContext
     */
    public EventContext getEnclosedContext() {
        return this.enclosedContext;
    }

    /**
     * @param value EventContext instance
     */
    public void setEnclosedContext(EventContext value) {
        this.enclosedContext = value;
    }

    /**
     * @param handler    handler
     * @param eventClass eventClass
     * @param <H>        <H>
     */
    public <H extends Event.EventHandler> void attach(H handler, Class<? extends Event<H>> eventClass) {
        this.registry.bind(handler, eventClass);
    }

    /**
     * @param handler    handler
     * @param eventClass eventClass
     * @param <H>        <H>
     */
    public <H extends Event.EventHandler> void detach(H handler, Class<? extends Event<H>> eventClass) {
        this.registry.unbind(handler, eventClass);
    }

    /**
     * @param handler      handler
     * @param requestClass requestClass
     * @param <H>          <H>
     */
    public <H extends Request.RequestHandler> void attach(H handler,
                                                          Class<? extends Request<H>> requestClass) {
        this.registry.bind(handler, requestClass);
    }

    /**
     * @param handler      handler
     * @param requestClass requestClass
     * @param <H>          <H>
     */
    public <H extends Request.RequestHandler> void detach(H handler,
                                                          Class<? extends Request<H>> requestClass) {
        this.registry.unbind(handler, requestClass);
    }

    /** */
    public void clearHandlers() {
        this.registry.clear();
    }

    /**
     * @param aClass  aClass
     * @param handler handler
     */
    public void bind(Class aClass, EventHandler handler) {
        this.registry.bind(handler, aClass);
    }

    /**
     * @param aClass  aClass
     * @param handler handler
     */
    public void unbind(Class aClass, EventHandler handler) {
        this.registry.unbind(handler, aClass);
    }

    /**
     * @param aClass aClass
     * @param <H>    <H>
     *
     * @return Collection
     */
    public <H> List<H> getHandlers(Class aClass) {
        return this.registry.getHandlers(aClass);
    }

    /**
     * @param event event
     * @param <H>   <H>
     * @param <E>   <E>
     *
     * @return <E>
     */
    public <H extends Event.EventHandler, E extends Event<H>> E post(E event) {
        try {
            List<H> handlers = getHandlers(event.getClass());

            if (handlers == null) {
                return event;
            }

            for (int i = 0, size = handlers.size(); i < size; ++i) {
                event.accept(handlers.get(i), this);
            }

            return event;
        } catch (Throwable e) {
            // todo: implement
            return event;
        }
    }

    /**
     * @param request request
     * @param <H>     <H>
     * @param <R>     <R>
     *
     * @return <R>
     *
     * @throws Request.RequestException requestException
     */
    public <H extends Request.RequestHandler, R extends Request<H>> R post(R request)
            throws Request.RequestException {
        try {
            List<H> handlers = getHandlers(request.getClass());

            if (handlers == null) {
                request.onNotHandled();
                return request;
            }

            for (int i = 0, size = handlers.size(); i < size; ++i) {
                if (request.accept(handlers.get(i), this)) {
                    return request;
                }
            }

            request.onNotHandled();

            return request;
        } catch (Request.RequestException e) {
            throw e;
        } catch (Throwable e) {
            throw new Request.RequestException(request, e);
        }
    }

    /**
     * @param key key
     *
     * @return Object instance
     */
    @SuppressWarnings("unchecked")
    public <T> T getAttribute(Object key) {
        return (T) this.attributes.get(key);
    }

    /**
     * @param key key
     *
     * @return Object instance
     */
    @SuppressWarnings("unchecked")
    public <T> T removeAttribute(Object key) {
        return (T) this.attributes.remove(key);
    }

    /**
     * @param key   key
     * @param value value
     *
     * @return CommonEventContext instance
     */
    public CommonEventContext setAttribute(Object key, Object value) {
        this.attributes.put(key, value);
        return this;
    }

    /**
     * @return Iterator<Object> instance
     */
    public Iterator<Object> attributes() {
        return this.attributes.keySet().iterator();
    }

    /** */
    public void clearAttributes() {
        this.attributes.clear();
    }

    @Override
    public String toString() {
        return "CommonEventContext {"
                + "name='" + this.name + '\''
                + '}';
    }
}
