//   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>
     */
    private boolean usageObserved;

    /**
     * <p>
     * the reference to the GUI model to which this GUI element belongs.
     * </p>
     */
    private GUIModel guiModel;
    
    /**
     * <p>
     * the hash code of this object
     * </p>
     */
    private int hashCode;

    /**
     * <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);
        
        if (specification != null) {
            this.hashCode = specification.hashCode();
        }
        else {
            this.hashCode = 0;
        }
    }

    /*
     * (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#getGUIModel()
     */
    @Override
    public GUIModel getGUIModel() {
       return guiModel;
    }

    /*
     * (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;
                    other.hashCode = this.hashCode;
                }
                else {
                    addIfNotContained(other.equalGUIElements, this);
                    this.equalGUIElements = other.equalGUIElements;
                    this.hashCode = other.hashCode;
                }
            }
            else {
                if (other.equalGUIElements == null) {
                    addIfNotContained(this.equalGUIElements, other);
                    other.equalGUIElements = this.equalGUIElements;
                    other.hashCode = this.hashCode;
                }
                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;
                        candidate.hashCode = this.hashCode;
                    }

                    other.equalGUIElements = this.equalGUIElements;
                    other.hashCode = this.hashCode;
                }
                // 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. 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(IGUIElement newParent) {
        synchronized (AbstractDefaultGUIElement.class) {
            // all equal GUI elements must have the same parent. Otherwise, they are not equal
            // anymore and we would have discrepancies on the return value of getParent() on
            // equal GUI elements.
            this.parent = newParent;
            if (equalGUIElements != null) {
                for (AbstractDefaultGUIElement candidate : equalGUIElements) {
                    candidate.parent = newParent;
                }
            }
        }
    }

    /**
     * <p>
     * used to set the GUI model to which this GUI element belongs. Will be set automatically, if
     * used in combination with {@link GUIModel};
     * </p>
     *
     * @param guiModel
     */
    void setGUIModel(GUIModel guiModel) {
        this.guiModel = guiModel;
    }

    /**
     * <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);
    }

}
