/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javafx.event;

// PENDING_DOC_REVIEW

import java.io.InvalidObjectException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;

/**
 * This class represents a specific event type associated with an {@code Event}.
 * <p>
 * Event types form a hierarchy with the {@link EventType#ROOT} (equals to
 * {@link Event#ANY}) as its root. This is useful in event filter / handler
 * registration where a single event filter / handler can be registered to a
 * super event type and will be receiving its sub type events as well.
 * Note that you cannot construct two different EventType objects with the same
 * name and parent.
 *
 * <p>
 * <b>Note about deserialization</b>: All EventTypes that are going to be deserialized
 * (e.g. as part of {@link Event} deserialization), need to exist at the time of
 * deserialization. Deserialization of EventType will not create new EventType
 * objects.
 *
 * @param <T> the event class to which this type applies
 * @since JavaFX 2.0
 */
public final class EventType<T extends Event> implements Serializable{

    /**
     * The root event type. All other event types are either direct or
     * indirect sub types of it. It is also the only event type which
     * has its super event type set to {@code null}.
     */
    public static final EventType<Event> ROOT =
            new EventType<Event>("EVENT", null);

    private WeakHashMap<EventType<? extends T>, Void> subTypes;

    private final EventType<? super T> superType;

    private final String name;

    /**
     * Constructs a new {@code EventType} with the {@code EventType.ROOT} as its
     * super type and the name set to {@code null}.
     * @deprecated Do not use this constructor, as only one such EventType can exist
     */
    @Deprecated
    public EventType() {
        this(ROOT, null);
    }

    /**
     * Constructs a new {@code EventType} with the specified name and the
     * {@code EventType.ROOT} as its super type.
     *
     * @param name the name
     * @throws IllegalArgumentException if an EventType with the same name and
     * {@link EventType#ROOT}/{@link Event#ANY} as parent
     */
    public EventType(final String name) {
        this(ROOT, name);
    }

    /**
     * Constructs a new {@code EventType} with the specified super type and
     * the name set to {@code null}.
     *
     * @param superType the event super type
     * @throws IllegalArgumentException if an EventType with "null" name and
     * under this supertype exists
     */
    public EventType(final EventType<? super T> superType) {
        this(superType, null);
    }

    /**
     * Constructs a new {@code EventType} with the specified super type and
     * name.
     *
     * @param superType the event super type
     * @param name the name
     * @throws IllegalArgumentException if an EventType with the same name and
     * superType exists
     */
    public EventType(final EventType<? super T> superType,
            final String name) {
        if (superType == null) {
            throw new NullPointerException(
                    "Event super type must not be null!");
        }

        this.superType = superType;
        this.name = name;
        superType.register(this);
    }

    /**
     * Internal constructor that skips various checks
     */
    EventType(final String name,
                      final EventType<? super T> superType) {
        this.superType = superType;
        this.name = name;
        if (superType != null) {
            if (superType.subTypes != null) {
                for (Iterator i = superType.subTypes.keySet().iterator(); i.hasNext();) {
                    EventType t  = (EventType) i.next();
                    if (name == null && t.name == null || (name != null && name.equals(t.name))) {
                        i.remove();
                    }
                }
            }
            superType.register(this);
        }
    }

    /**
     * Gets the super type of this event type. The returned value is
     * {@code null} only for the {@code EventType.ROOT}.
     *
     * @return the super type
     */
    public final EventType<? super T> getSuperType() {
        return superType;
    }

    /**
     * Gets the name of this event type.
     *
     * @return the name
     */
    public final String getName() {
        return name;
    }

    /**
     * Returns a string representation of this {@code EventType} object.
     * @return a string representation of this {@code EventType} object.
     */
    @Override
    public String toString() {
        return (name != null) ? name : super.toString();
    }

    private void register(javafx.event.EventType<? extends T> subType) {
        if (subTypes == null) {
            subTypes = new WeakHashMap<EventType<? extends T>, Void>();
        }
        for (EventType<? extends T> t : subTypes.keySet()) {
            if (((t.name == null && subType.name == null) || (t.name != null && t.name.equals(subType.name)))) {
                throw new IllegalArgumentException("EventType \"" + subType + "\""
                        + "with parent \"" + subType.getSuperType()+"\" already exists");
            }
        }
        subTypes.put(subType, null);
    }

    private Object writeReplace() throws ObjectStreamException {
        Deque<String> path = new LinkedList<String>();
        EventType<?> t = this;
        while (t != ROOT) {
            path.addFirst(t.name);
            t = t.superType;
        }
        return new EventTypeSerialization(new ArrayList<String>(path));
    }

    static class EventTypeSerialization implements Serializable {
        private List<String> path;

        public EventTypeSerialization(List<String> path) {
            this.path = path;
        }

        private Object readResolve() throws ObjectStreamException {
            EventType t = ROOT;
            for (int i = 0; i < path.size(); ++i) {
                String p = path.get(i);
                if (t.subTypes != null) {
                    EventType s = findSubType(t.subTypes.keySet(), p);
                    if (s == null) {
                        throw new InvalidObjectException("Cannot find event type \"" + p + "\" (of " + t + ")");
                    }
                    t = s;
                } else {
                    throw new InvalidObjectException("Cannot find event type \"" + p + "\" (of " + t + ")");
                }
            }
            return t;
        }

        private EventType findSubType(Set<EventType> subTypes, String name) {
            for (EventType t : subTypes) {
                if (((t.name == null && name == null) || (t.name != null && t.name.equals(name)))) {
                    return t;
                }
            }
            return null;
        }

    }
}
