package hirs.appraiser;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;

/**
 * The <code>Appraiser</code> class represents an appraiser that can appraise a <code>Report</code>.
 * <code>Appraiser</code>s are invoked to validate the integrity of client's platform. An
 * <code>Appraiser</code> does this by examining a <code>Report</code> sent from the client's
 * machine.
 * <p>
 * Supported <code>Report</code> types are kept track of in three ways: <ul> <li>The type of report
 * received for appraisal is getAppraiseReportType() (e.g. the <code>DeviceInfoAppraiser</code>
 * takes in a <code>DeviceInfoReport</code> and the <code>TPMAppraiser</code> takes in an
 * <code>IntegrityReport</code>)</li> <li>The type requested in getReportRequest is
 * getRequestReportType(). This tends to be the specific report type for that type of appraiser
 * (e.g. the <code>IMAAppraiser</code> requests an <code>IMAReport</code> and the
 * <code>TPMAppraiser</code> requests a <code>TPMReport</code>)</li> <li>The set of types this
 * appraiser relies on extracting from the top-level report is getRequiredReportTypes() (e.g. if the
 * top-level report is <code>IntegrityReport</code> then the <code>IMAAppraiser</code> needs to
 * extract both a <code>DeviceInfoReport</code> and a <code>IMAReport</code> from the
 * <code>IntegrityReport</code>)</li> </ul>
 */
@Entity
@Table(name = "Appraiser")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Appraiser {

    private static final Logger LOGGER = LogManager.getLogger(Appraiser.class);

    @Id
    @Column(name = "Appraiser_ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false, unique = true)
    private String name;

    /**
     * Creates a new <code>Appraiser</code> with the specified name. The name should be universally
     * unique as this is how other components will identify <code>Appraiser</code>s. Web portals,
     * for instance, could display a list of <code>Appraiser</code> names to display which
     * <code>Appraiser</code>s are available.
     * <p>
     * The name will be tested for uniqueness when it is added to a repository. It is not tested for
     * uniqueness in the class.
     *
     * @param name unique name
     */
    public Appraiser(final String name) {
        setName(name);
    }

    /**
     * Default constructor necessary for Hibernate.
     */
    protected Appraiser() {
        /* do nothing */
    }

    /**
     * Returns the ID that is auto-generated when the Appraiser is stored using Hibernate. May be
     * null if the Appraiser hasn't been stored.
     *
     * @return ID auto-generated by Hibernate, null if not stored
     */
    public final Long getId() {
        return this.id;
    }

    /**
     * Returns a <code>String</code> that uniquely identifies this <code>Appraiser</code>.
     *
     * @return unique name for this <code>Appraiser</code>
     */
    public String getName() {
        return this.name;
    }

    /**
     * Sets the name that uniquely identifies this <code>Appraiser</code>. The name may not be
     * null.
     *
     * @param name unique name for this <code>Appraiser</code>
     */
    public final void setName(final String name) {
        if (name == null) {
            LOGGER.error("null name argument");
            throw new NullPointerException("name");
        }
        this.name = name;
    }

    /**
     * Calculates the hash code. The hash code is calculated from the name.
     * <p>
     * <b>NOTE:</b> This class is mutable and the name property may be set. Be careful not to set
     * the name of an <code>Appraiser</code> that is used as a key in a hash mapper.
     *
     * @return hash code
     */
    @Override
    public final int hashCode() {
        final int prime = 73;
        final int prime2 = 251;
        int result = prime2;
        result = prime * result + name.hashCode();
        return result;
    }

    /**
     * Tests for equality. This returns true if the other object is not null, an instance of an
     * <code>Appraiser</code>, and their names are equal.
     *
     * @param obj other object to test for equality
     * @return true if equal, otherwise false
     */
    @Override
    public final boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof Appraiser)) {
            return false;
        }
        Appraiser other = (Appraiser) obj;
        return name.equals(other.name);
    }

    @Override
    public final String toString() {
        return this.name;
    }

}
