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

import java.util.List;

import de.ugoe.cs.autoquest.eventcore.IEventTargetSpec;
import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec;

/**
 * <p>
 * Implements the {@link IGUIElementSpec} for {@link ANDROIDGUIElement}s.
 * </p>
 * 
 * @version 1.0
 * @author Florian Unger
 */
public class ANDROIDGUIElementSpec implements IGUIElementSpec {

    /**
     * <p>
     * Default serial version UID
     * </p>
     */
    private static final long serialVersionUID = 1L;

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.autoquest.androidmonitor.AndroidmonitorLogFile#logComponent()
     */
    /**
     * <p>
     * Hash code of the GUI element. Used as unique identifier during parsing a log file. Note that
     * it is possible that the hash code of an element changes over several log files even if they
     * come from the same target.
     * </p>
     */
    private int elementHash;

    /**
     * <p>
     * Path to an element in an activity. e.g. a path of a button could look like
     * MainActivity/DecorView/ActionBarOverlayLayout/FrameLayout/RelativeLayout/Button
     * </p>
     */
    private String path;

    /*
     * (non-Javadoc)
     * 
     * @see http://developer.android.com/reference/android/view/View.html#getId()
     */
    /**
     * <p>
     * Id of the object as it is returned by view.getId().
     * </p>
     */
    private int index;

    /**
     * <p>
     * Current name of the GUI element
     * </p>
     */
    private String name;

    /**
     * <p>
     * The type of GUI element, i.e., the class of the android GUI element.
     * </p>
     */
    private String type;

    /**
     * <p>
     * The position of the element in the original GUI.
     * </p>
     */
    private int elementPosition;

    /**
     * <p>
     * Type hierarchy of the class itself.
     * </p>
     */
    private List<String> typeHierarchy = null;

    /*
     * (non-Javadoc)
     * 
     * @see
     * de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec#getSecificationSimilarity(IGUIElementSpec
     * )
     */
    @Override
    public boolean getSimilarity(IEventTargetSpec other) {
        if (this == other) {
            return true;
        }

        if (!(other instanceof ANDROIDGUIElementSpec)) {
            return false;
        }

        ANDROIDGUIElementSpec otherSpec = (ANDROIDGUIElementSpec) other;

        if (type == null ? otherSpec.type != null : !type.equals(otherSpec.type)) {
            return false;
        }

        /*
         * Up to now, we compared, if the basics match. Due to testing with different virtual
         * devices it seems to be the case that the id of a view (named index here) keeps the same
         * even on different devices even if the hashCode changes. Some of the GUI elements does not
         * have an id (id is -1).
         */

        if (otherSpec.getIndex() > 1 && getIndex() == otherSpec.getIndex()) {
            return true;
        }

        /*
         * Path and label of the elements fits together. In this case it is most likely that this
         * elements fits together. This only makes sense in the case a label exists.
         */
        if (!"NOT_SET".equals(otherSpec.getName()) && !"NOT_SET".equals(getName()) &&
            !otherSpec.getName().contains("image:") && !getName().contains("image:") &&
            otherSpec.getName().equals(getName()) && otherSpec.getPath().equals(getPath()))
        {
            return true;
        }

        /*
         * Path and position fits together. In this case it is most likely that this elements fits
         * together.
         */
        if (otherSpec.getPath().equals(getPath()) &&
            otherSpec.getElementPosition() == getElementPosition())
        {
            return true;
        }

        /*
         * Two elements could also be similar if the path of the elements is equal and the name is
         * set, equal and not equal to "image:" even if index <= 1. This comparison is not
         * implemented up to know due to the reason that all recorded elements up to 2015/01 either
         * have an index > 1 or no name to be compared.
         * 
         * In all other cases up to know it is not clear if two elements are similar.
         * 
         * Not working:
         * 
         * - Position of the elements: Due to the reason that there are a lot of different displays
         * on the android market and the in the most cases the layout depends on the display size
         * (different resolutions) similar elements have different positions.
         */

        return false;
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec#getType()
     */
    @Override
    public String getType() {
        return type;
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec#getTypeHierarchy ()
     */
    @Override
    public String[] getTypeHierarchy() {
        if (typeHierarchy == null) {
            return new String[]
                { (getType()) };
        }
        else
            return typeHierarchy.toArray(new String[typeHierarchy.size()]);
    }

    /**
     * <p>
     * Returns the object hash of the specified GUI element.
     * </p>
     * 
     * @return the elementHash
     */
    public int getElementHash() {
        return elementHash;
    }

    /**
     * <p>
     * Returns the path associated with the specified GUI element.
     * </p>
     * 
     * @return the path to an element
     */
    public String getPath() {
        return path;
    }

    /**
     * <p>
     * Returns the GUI element identifier.
     * </p>
     * 
     * @return identifier of the GUI element
     */
    public int getIndex() {
        return index;
    }

    /**
     * <p>
     * Returns the name of the specified GUI element. Displayed text in the application or image
     * name.
     * </p>
     * 
     * @return text or image of the GUI element.
     */
    public String getName() {
        if (name == null || name.trim().length() == 0) {
            return "NOT_SET";
        }
        return name;
    }

    /**
     * 
     * <p>
     * Return the position of the element in the original GUI.
     * </p>
     * 
     * @return position of the element in the original GUI.
     */
    public int getElementPosition() {
        return elementPosition;
    }

    /**
     * 
     * <p>
     * Set the position of the element in the original GUI.
     * </p>
     */
    public void setElementPosition(int elementPosition) {
        this.elementPosition = elementPosition;
    }

    /**
     * <p>
     * Sets the GUI element identifier.
     * </p>
     * 
     * @param indentifier
     */
    public void setIndex(int index) {
        this.index = index;
    }

    /**
     * Set the hash code associated with the GUI element.
     * 
     * @param hash
     *            the hash of an element object
     */
    public void setElementHash(int hash) {
        this.elementHash = hash;
    }

    /**
     * Set the path associated with the specified GUI element.
     * 
     * @param path
     *            the path to an element
     */
    public void setPath(String path) {
        this.path = path;
    }

    /**
     * <p>
     * Sets the type of the specified GUI element.
     * </p>
     * 
     * @param type
     *            the type
     */
    public void setType(String type) {
        this.type = type;
    }

    /**
     * <p>
     * Sets the name of the specified GUI element. Displayed text in the application or image name.
     * </p>
     * 
     * @param name
     *            the name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * <p>
     * Sets the type hierarchy of the specified GUI element.
     * </p>
     */
    public void setTypeHierarchy(List<String> typeHierarchy) {
        this.typeHierarchy = typeHierarchy;
    }

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec#equals(IGUIElementSpec)
     */
    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }

        if (!(other instanceof ANDROIDGUIElementSpec)) {
            return false;
        }

        ANDROIDGUIElementSpec otherSpec = (ANDROIDGUIElementSpec) other;

        return (elementHash == otherSpec.elementHash) &&
            (path == null ? otherSpec.path == null : path.equals(otherSpec.path)) &&
            (index == otherSpec.index) &&
            (elementPosition == otherSpec.elementPosition) &&
            (name == null ? otherSpec.name == null : name.equals(otherSpec.name)) &&
            (type == null ? otherSpec.type == null : type.equals(otherSpec.type));
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        // 17 due to the reason that this is a prime number.
        int result = 17;
        /*
         * 31 due to the reason that a lot of VM's could optimize this multiplication by a shift.
         * Source: Effective Java, Joshua Bloch, 2008, p.48
         */
        result = 31 * result + elementHash;
        result = 31 * result + path.hashCode();
        result = 31 * result + index;
        result = 31 * result + getName().hashCode();
        result = 31 * result + type.hashCode();
        result = 31 * result + elementPosition;
        return result;
    }

}
