package de.ugoe.cs.quest.eventcore.guimodel;

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

/**
 * TODO comment
 * 
 * @version $Revision: $ $Date: $
 * @author 2011, last modified by $Author: $
 */
public abstract class AbstractDefaultGUIElement implements IGUIElement {
    
    /**  */
    public static final long serialVersionUID = 1L;

    /** the reference to equal GUI element manager (needed to preserve singleton behavior) */
    private static final EqualGUIElementManager equalGUIElementManager =
        new EqualGUIElementManager();

    /** the specification of the GUI element */
    private IGUIElementSpec specification;

    /** the reference to the parent element */
    private IGUIElement parent;

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param specification
     */
    public AbstractDefaultGUIElement(IGUIElementSpec specification, IGUIElement parent) {
        this.specification = specification;
        this.parent = parent;
    }

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

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

    /* (non-Javadoc)
     * @see de.ugoe.cs.quest.eventcore.guimodel.IGUIElement#addEqualGUIElement(IGUIElement)
     */
    @Override
    public void addEqualGUIElement(IGUIElement equalElement)
    {
        equalGUIElementManager.addEqualGUIElements(this, equalElement);
    }

    /*
     * (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
        return super.equals(other) || equalGUIElementManager.equals(this, other);
    }

    /* (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
        return equalGUIElementManager.hashCode(this);
    }

    /**
     * <p>
     * TODO comment
     * </p>
     * 
     * @version $Revision: $ $Date: 24.08.2012$
     * @author 2012, last modified by $Author: pharms$
     */
    private static class EqualGUIElementManager {

        /**
         * <p>
         * the internal map of GUI elements mapping each registered element to its equal variants.
         * We use the {@link IdentityHashMap} as a normal hash map would not work because of the
         * changing of the hash code of GUI elements at runtime.
         * </p>
         */
        private IdentityHashMap<IGUIElement, List<IGUIElement>> identityHashMap =
            new IdentityHashMap<IGUIElement, List<IGUIElement>>();
        
        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @param abstractDefaultGUIElement
         * @param equalElement
         */
        private synchronized void addEqualGUIElements(IGUIElement guiElement1,
                                                      IGUIElement guiElement2)
        {
            List<IGUIElement> list1 = identityHashMap.get(guiElement1);
            List<IGUIElement> list2 = identityHashMap.get(guiElement2);
            
            if (list1 == null) {
                if (list2 == null) {
                    list2 = new LinkedList<IGUIElement>();
                    list2.add(guiElement1);
                    list2.add(guiElement2);
                    identityHashMap.put(guiElement1, list2);
                    identityHashMap.put(guiElement2, list2);
                }
                else {
                    addIfNotContained(list2, guiElement1);
                    identityHashMap.put(guiElement1, list2);
                }
            }
            else {
                if (list2 == null) {
                    addIfNotContained(list1, guiElement2);
                    identityHashMap.put(guiElement2, list1);
                }
                else if (list1 != list2) {
                    list1.addAll(list2);
                    identityHashMap.put(guiElement2, list1);
                }
                // else
                //     in this case, both GUI elements should already be registered with the same
                //     lists.
            }
        }

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @param abstractDefaultGUIElement
         * @return
         */
        private synchronized int hashCode(IGUIElement guiElement) {
            return System.identityHashCode(getEqualElementsList(guiElement));
        }

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @param abstractDefaultGUIElement
         * @param other
         * @return
         */
        private synchronized boolean equals(IGUIElement guiElement, Object other) {
            if (other instanceof IGUIElement) {
                for (IGUIElement candidate : getEqualElementsList(guiElement)) {
                    if (candidate == other) {
                        return true;
                    }
                }
            }
            
            return false;
        }

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @param guiElement
         * @return
         */
        private List<IGUIElement> getEqualElementsList(IGUIElement guiElement) {
            List<IGUIElement> returnValue = identityHashMap.get(guiElement);
            
            if (returnValue == null) {
                returnValue = new LinkedList<IGUIElement>();
                returnValue.add(guiElement);
                identityHashMap.put(guiElement, returnValue);
            }
            
            return returnValue;
        }

        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @param resultingList
         * @param guiElement1
         */
        private void addIfNotContained(List<IGUIElement> equalElementsList, IGUIElement guiElement)
        {
            for (IGUIElement candidate : equalElementsList) {
                if (candidate == guiElement) {
                    return;
                }
            }
            
            equalElementsList.add(guiElement);
        }

    }

}
