//   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.guimodel;

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

/**
 * <p>
 * Skeletal implementation for GUI elements.
 * </p>
 * 
 * @version 1.0
 * @author Patrick Harms
 */
public abstract class AbstractDefaultGUIElement implements IGUIElement {

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

    /**
     * <p>
     * Specification of the GUI element
     * </p>
     */
    private final IGUIElementSpec specification;

    /**
     * <p>
     * Reference to the parent element
     * </p>
     */
    private IGUIElement parent;
    
    /**
     * <p>
     * List of other GUI elements being equal to this
     * </p>
     */
    private List<AbstractDefaultGUIElement> equalGUIElements = null;
    
    /**
     * <p>
     * Boolean that indicates if a GUIElement was used during a session.
     * </p>
     */
    boolean usageObserved;

    /**
     * <p>
     * Constructor. Creates a new AbstractDefaultGUIElement.
     * </p>
     * 
     * @param specification
     *            specification of the created GUI element
     * @param parent
     *            parent of the created GUI element; null means the element is a top-level window
     */
    public AbstractDefaultGUIElement(IGUIElementSpec specification, IGUIElement parent) {
        this.specification = specification;
        this.usageObserved = false;
        setParent(parent);
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.tasktree.guimodel.GUIElement#getSpecification()
     */
    @Override
    public IGUIElementSpec getSpecification() {
        return specification;
    }

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

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

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

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

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement#isUsed()
     */
    @Override
    public boolean isUsed() {
        return usageObserved;
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement#markUsed()
     */
    @Override
    public void markUsed() {
        this.usageObserved = true;
    }

    /*
     * (non-Javadoc)
     * 
     * @see GUIElement#equals(GUIElement)
     */
    public final boolean equals(Object other) {
        // implement final, as GUI elements are all singletons and they equal only if they are the
        // same object or if they are in the list of equal GUI elements
        if (super.equals(other)) {
            return true;
        }
        else if (other instanceof AbstractDefaultGUIElement) {
            synchronized (AbstractDefaultGUIElement.class) {
                if (equalGUIElements != null) {
                    for (IGUIElement candidate : equalGUIElements) {
                        if (candidate == other) {
                            return true;
                        }
                    }
                }
            }
        }

        return false;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#hashCode()
     */
    @Override
    public final int hashCode() {
        // implement final, as GUI elements are all singletons and they equal only if they are the
        // same object. If there are several GUI element objects that represent the same GUI element
        // then they are stored in the list of equal elements. In this case, the hash code of the
        // list is unique within the system
        synchronized (AbstractDefaultGUIElement.class) {
            if (this.equalGUIElements == null) {
                this.equalGUIElements = new LinkedList<AbstractDefaultGUIElement>();
                this.equalGUIElements.add(this);
            }
            
            return System.identityHashCode(this.equalGUIElements);
        }
    }
    
    /**
     * <p>
     * updates the parent node of this node if required due to model restructuring
     * </p>
     */
    void setParent(IGUIElement newParent) {
        this.parent = newParent;
    }

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

        equalElementsList.add(guiElement);
    }

}
