//   Copyright 2012 Georg-August-Universität Göttingen, Germany
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package de.ugoe.cs.autoquest.eventcore;

import java.util.LinkedList;
import java.util.List;

/**
 * <p>
 * Skeletal implementation for hierarchical event targets.
 * </p>
 * 
 * @version 1.0
 * @author Patrick Harms
 */
public abstract class AbstractDefaultHierarchicalEventTarget
    implements IHierarchicalEventTarget
{

    /**
     * <p>
     * Id for object serialization.
     * </p>
     */
    public static final long serialVersionUID = 1L;

    /**
     * <p>
     * Specification of the event target
     * </p>
     */
    private final IEventTargetSpec specification;

    /**
     * <p>
     * Reference to the parent event target
     * </p>
     */
    private IHierarchicalEventTarget parent;
    
    /**
     * <p>
     * List of other event targets being equal to this
     * </p>
     */
    private List<AbstractDefaultHierarchicalEventTarget> equalEventTargets = null;

    /**
     * <p>
     * the reference to the event target model to which this event target belongs.
     * </p>
     */
    private HierarchicalEventTargetModel<?> eventTargetModel;
    
    /**
     * <p>
     * the hash code of this object
     * </p>
     */
    private int hashCode;

    /**
     * <p>
     * Constructor. Creates a new AbstractDefaultEventTarget.
     * </p>
     * 
     * @param specification
     *            specification of the created event target
     * @param parent
     *            parent of the created event target; null means the target is at top-level
     */
    public AbstractDefaultHierarchicalEventTarget(IEventTargetSpec         specification,
                                                  IHierarchicalEventTarget parent)
    {
        this.specification = specification;
        setParent(parent);
        
        if (specification != null) {
            this.hashCode = specification.hashCode();
        }
        else {
            this.hashCode = 0;
        }
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.eventcore.IHierarchicalEventTarget#getSpecification()
     */
    @Override
    public IEventTargetSpec getSpecification() {
        return specification;
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.eventcore.IHierarchicalEventTarget#getParent()
     */
    @Override
    public IHierarchicalEventTarget getParent() {
        return parent;
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.eventcore.IHierarchicalEventTarget#getEventTargetModel()
     */
    @Override
    public HierarchicalEventTargetModel<?> getEventTargetModel()
    {
       return eventTargetModel;
    }

    /* (non-Javadoc)
     * @see de.ugoe.cs.autoquest.eventcore.IHierarchicalEventTarget#addEqualEventTarget(IHierarchicalEventTarget)
     */
    @Override
    public void addEqualEventTarget(IHierarchicalEventTarget equalElement) {
        if (!(equalElement instanceof AbstractDefaultHierarchicalEventTarget)) {
            throw new IllegalArgumentException
                ("this implementation can only handle other AbstractDefaultHierarchicalEventTargets");
        }
        
        AbstractDefaultHierarchicalEventTarget other =
            (AbstractDefaultHierarchicalEventTarget) equalElement;
        
        synchronized (AbstractDefaultHierarchicalEventTarget.class) {
            if (this.equalEventTargets == null) {
                if (other.equalEventTargets == null) {
                    this.equalEventTargets = new LinkedList<AbstractDefaultHierarchicalEventTarget>();
                    this.equalEventTargets.add(this);
                    this.equalEventTargets.add(other);
                    other.equalEventTargets = this.equalEventTargets;
                    other.hashCode = this.hashCode;
                }
                else {
                    addIfNotContained(other.equalEventTargets, this);
                    this.equalEventTargets = other.equalEventTargets;
                    this.hashCode = other.hashCode;
                }
            }
            else {
                if (other.equalEventTargets == null) {
                    addIfNotContained(this.equalEventTargets, other);
                    other.equalEventTargets = this.equalEventTargets;
                    other.hashCode = this.hashCode;
                }
                else if (this.equalEventTargets != other.equalEventTargets) {
                    this.equalEventTargets.addAll(other.equalEventTargets);

                    // we also have to set this new list for all other elements for which so
                    // far list2 was registered
                    for (AbstractDefaultHierarchicalEventTarget candidate : other.equalEventTargets) {
                        candidate.equalEventTargets = this.equalEventTargets;
                        candidate.hashCode = this.hashCode;
                    }

                    other.equalEventTargets = this.equalEventTargets;
                    other.hashCode = this.hashCode;
                }
                // else
                // in this case, both GUI elements should already be registered with the same
                // lists.
            }
        }
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public final boolean equals(Object other) {
        // implement final, as hierarchical event targets are all singletons and they equal only
        // if they are the same object or if they are in the list of equal event targets
        if (super.equals(other)) {
            return true;
        }
        else if (other instanceof AbstractDefaultHierarchicalEventTarget) {
            synchronized (AbstractDefaultHierarchicalEventTarget.class) {
                if (equalEventTargets != null) {
                    for (AbstractDefaultHierarchicalEventTarget candidate : equalEventTargets) {
                        if (candidate == other) {
                            return true;
                        }
                    }
                }
            }
        }

        return false;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public final int hashCode() {
        // implement final, as event targets are all singletons and they equal only if they are the
        // same object. If there are several event target objects that represent the same event target
        // then they are stored in the list of equal event target. But at least their type is expected
        // to be equal, so return the hash code of the type.
        return hashCode;
    }
    
    /**
     * <p>
     * updates the parent node of this node if required due to model restructuring
     * </p>
     */
    void setParent(IHierarchicalEventTarget newParent)
    {
        synchronized (AbstractDefaultHierarchicalEventTarget.class) {
            // all equal event targets must have the same parent. Otherwise, they are not equal
            // anymore and we would have discrepancies on the return value of getParent() on
            // equal event targets.
            this.parent = newParent;
            if (equalEventTargets != null) {
                for (AbstractDefaultHierarchicalEventTarget candidate : equalEventTargets) {
                    candidate.parent = newParent;
                }
            }
        }
    }

    /**
     * <p>
     * used to set the event target model to which this event target belongs. Will be set
     * automatically, if used in combination with {@link HierarchicalEventTargetModel};
     * </p>
     *
     * @param eventTargetModel
     * 
     */
    void setEventTargetModel(HierarchicalEventTargetModel<?> eventTargetModel) {
        this.eventTargetModel = eventTargetModel;
    }

    /**
     * <p>
     * Adds an {@link AbstractDefaultHierarchicalEventTarget} as equal to a list of
     * {@link AbstractDefaultHierarchicalEventTarget}s if and only if it is not already contained.
     * </p>
     * 
     * @param equalTargetsList
     *            list of {@link AbstractDefaultHierarchicalEventTarget} to which the event target
     *            is added
     * @param eventTarget
     *            event target to be added
     */
    private void addIfNotContained(List<AbstractDefaultHierarchicalEventTarget> equalTargetsList,
                                   AbstractDefaultHierarchicalEventTarget       eventTarget)
    {
        for (AbstractDefaultHierarchicalEventTarget candidate : equalTargetsList) {
            if (candidate == eventTarget) {
                return;
            }
        }

        equalTargetsList.add(eventTarget);
    }

}
