package lc.mecha.event;

import java.util.HashSet;
import java.util.Set;

/**
 * This class represents an event which has been sent into an <code>EventTarget</code>.
 *
 * @author Alex Leigh
 * @since mk11 (ZERO CONTRAST)
 */
public class Event {
    private final String eventType;
    private EventBaseObject originalTarget;
    private EventBaseObject currentTarget;
    private boolean isCancelled = false;

    /**
     * Create a new event for the given target.
     */
    public Event(EventBaseObject originalTarget, String eventType) {
        this.originalTarget = originalTarget;
        this.currentTarget = originalTarget;
        this.eventType = eventType;
    }

    /**
     * Cancel propagation of the given event. All listeners for the current target will execute, but the event
     * will not propagate any further.
     */
    public void cancelBubble() {
        isCancelled = true;
    }

    /**
     * Return the event type associated with this <code>EOEvent</code>.
     */
    public String getEventType() {
        return eventType;
    }

    /**
     * Return the original target for this <code>EOEvent</code>. This is the EO object or loop which the event
     * was originally targeted for.
     */
    public EventBaseObject getOriginalTarget() {
        return originalTarget;
    }

    /**
     * Set the original target for this <code>EOEvent</code>.
     */
    void setOriginalTarget(EventBaseObject originalTarget) {
        this.originalTarget = originalTarget;
    }

    /**
     * Dispatch the given event. This will start propagation of the event at <code>originalTarget</code> and
     * it will go from there.
     * <p>
     * If one of the listeners cancels the event by calling the <code>cancelBubble()</code> method, all handlers
     * for that target will still run, but the event will not propagate further. If the target is a member of a
     * cyclic graph and the event bubbles back to <code>originalTarget</code> then propagation will cease.
     */
    public boolean dispatch() {
        Set<Event> e = new HashSet<>();
        e.add(this);

        while (currentTarget != null) {
            Set<EventListener> listeners = currentTarget.getListeners(eventType);
            if (listeners != null) {
                for (EventListener l : listeners) {
                    l.handle(e);
                }
            }

            if (isCancelled) break;

            currentTarget = currentTarget.getParent();
            if (currentTarget == null || currentTarget == originalTarget) break;
        }

        return isCancelled();
    }

    /**
     * Returns true if the event has been cancelled by at least one event listener.
     */
    public boolean isCancelled() {
        return isCancelled;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Event event = (Event) o;

        if (isCancelled != event.isCancelled) return false;
        if (eventType != null ? !eventType.equals(event.eventType) : event.eventType != null) return false;
        return originalTarget != null ? originalTarget.equals(event.originalTarget) : event.originalTarget == null;
    }

    @Override
    public int hashCode() {
        int result = eventType != null ? eventType.hashCode() : 0;
        result = 31 * result + (originalTarget != null ? originalTarget.hashCode() : 0);
        result = 31 * result + (isCancelled ? 1 : 0);
        return result;
    }

    @Override
    public String toString() {
        return "Event{" +
                "eventType='" + eventType + '\'' +
                ", originalTarget=" + originalTarget +
                ", currentTarget=" + currentTarget +
                ", isCancelled=" + isCancelled +
                '}';
    }

}
