package com.quui.tm2;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.net.URL;

import org.apache.commons.lang.NotImplementedException;

/**
 * The interchange format of a text mining application: an annotation of data with meta-data.
 * <p/>
 * This class is immutable:
 * <ol>
 * <li>It can't be extended: it is final and has just a private constructor</li>
 * <li>All fields are final</li>
 * <li>All fields are private</li>
 * <li>On construction, the BigInteger objects are secured such that a new 'real' BigInteger is
 * created from they bytes of the given values</li>
 * <li>Parameters and return values of accessors are defensively copied</li>
 * </ol>
 * <p/>
 * Annotations therefore are inherently thread-safe and can be shared and reused freely (cf. Bloch
 * 2008, i.e. Effective Java, 2nd Ed., Item 15)
 * @author Fabian Steeg (fsteeg)
 * @param <T> The type of the meta-data annotating some data
 */
public final class ImmutableAnnotation<T extends Comparable<T>> implements Annotation<T> {
    /***/
    private static final long serialVersionUID = -4685629518898888821L;
    /*
     * TODO As the constructor is private, the class is final, but should it be? TODO Should an
     * annotation hold some kind of reference to the actual signal?
     */
    /***/
    private final T value;
    /***/
    private final BigInteger start;
    /***/
    private final BigInteger end;
    private Class<? extends Agent<?, ?>> author;
    /***/
    private static final int HASH_FACTOR = 31;
    /***/
    private static final int HASH_START = 17;

    /**
     * We make the constructor private and use a factory method to avoid redundant generics
     * specification.
     * @param <T> The annotation value type
     * @param author The class of the agent authoring this annotation
     * @param value The annotation value
     * @param start The start index
     * @param end The end index
     * @return Returns an annotation typed to the type of the value, with the specified range
     */
    public static <T extends Comparable<T>> Annotation<T> getInstance(
    		//TODO should be Agent<?,T>
            final Class<? extends Agent<?, ?>> author, final T value, final BigInteger start,
            final BigInteger end) {
        return new ImmutableAnnotation<T>(author, value, start, end);
    }

    /**
     * Convenience factory method for small texts, using int values for indices.
     * @param <T> The annotation value type
     * @param author The class of the agent authoring this annotation
     * @param value The annotation value
     * @param start The start index
     * @param end The end index
     * @return Returns an annotation typed to the type of the value, with the specified range
     */
    public static <T extends Comparable<T>> ImmutableAnnotation<T> getInstance(
            final Class<? extends Agent<?, ?>> author, final T value, final int start, final int end) {
        return new ImmutableAnnotation<T>(author, value, BigInteger.valueOf(start),
                BigInteger.valueOf(end));
    }

    /**
     * @param value The value this annotation should represent
     * @param start The starting index in the signal (inclusive)
     * @param end The ending index in the signal (exclusive)
     */
    private ImmutableAnnotation(final Class<? extends Agent<?, ?>> author, final T value,
            final BigInteger start, final BigInteger end) {
        this.author = author;
        this.value = value;// TODO: newInstance(value);
        this.start = newInstance(start);
        this.end = newInstance(end);
    }

    public ImmutableAnnotation(Annotation<T> annotation) {
        this(annotation.author(), annotation.getValue(), annotation.getStart(), annotation.getEnd());
    }

    /**
     * @param big The BigInteger to copy
     * @return Returns a defensive copy safely non-mutable (real) BigInteger, not any subclass
     */
    private BigInteger newInstance(final BigInteger big) {
        return new BigInteger(big.toByteArray());
    }

    /**
     * @return Returns the annotation value
     */
    public T getValue() {
        return value;// TODO: newInstance(value);
    }

    /**
     * @param val The value to create a new instance for
     * @return Returns a new reflective instance for val
     */
    private T newInstance(final T val) {
        /*
         * It's a T, so casting the class to a class of T can't go wrong...
         */
        @SuppressWarnings( "unchecked" ) Class<T> clazz = (Class<T>) val.getClass();
        try {
            /* As elsewhere, we assume a String constructor */
            Constructor<T> constructor = clazz.getConstructor(String.class);
            /* That will create a valid instance from a toString representation */
            T instance = constructor.newInstance(val.toString());
            /*
             * TODO We need an early place to check if an agent (or an annotation, interaction...
             * maybe inside an experiment) produces usable values. Makes me wonder about using an
             * abstract class instead of a constructor again...
             */
            return instance;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @return Returns the annotation's end index
     */
    public BigInteger getEnd() {
        return newInstance(end);
    }

    /**
     * @return Returns the annotation's start index
     */
    public BigInteger getStart() {
        return newInstance(start);
    }

    /**
     * Clients should not use equals to compare annotations, as it is not type-safe. Rather use
     * compareTo directly, which will ensure only comparable types are compared
     * @return Returns true if this annotation equals obj
     * @param obj The object to compare this annotation to
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(final Object obj) {
        /* If it ain't no annotation at all it ain't equal */
        if (!(obj instanceof ImmutableAnnotation<?>)) {
            return false;
        }
        /*
         * The actual class cast exception won't be thrown from here, but from the compare to
         * method; we don't catch it as it seems to be just what we want to tell a client who calls
         * equals on annotations of different types. (TODO as opposed to lists of different types...
         * an experienced Java programmer might expect them to be equal, as lists are, but equals
         * and compareTo should be consistent... And just because generics are reified in current
         * Java does not make annotations of different types equal)
         */
        Annotation<?> a = (Annotation<?>) obj;
        if (!(this.getValue().getClass().equals(a.getValue().getClass()))) {
            return false;
        }
        @SuppressWarnings( "unchecked" ) ImmutableAnnotation<T> castedAnnotation = (ImmutableAnnotation<T>) obj;
        /* Consistent with compareTo: */
        return this.compareTo(castedAnnotation) == 0;
    }

    /**
     * Implemented as suggested in Bloch 2008, Item 9.
     * @return Returns a hash code for this annotation
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        int result = HASH_START;
        result = HASH_FACTOR * result + value.hashCode();
        result = HASH_FACTOR * result + start.hashCode();
        result = HASH_FACTOR * result + end.hashCode();
        return result;
    }

    /**
     * @return Returns a user-readable representation of this annotation, containing the value and
     *         the start and end indices. The exact format is subject to change. If you need the
     *         individual properties of this annotation, use the appropriate accessors.
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return "'" + value + "' from '" + start + "' to '" + end + "'";
    }

    /**
     * Compares this annotation to the other annotation according to three ordered criteria:
     * <ol>
     * <li>Actual value</li>
     * <li>Start index</li>
     * <li>End index</li>
     * </ol>
     * This means annotations with different values will be sorted according to these values, if
     * they have the same value they will be sorted according to their start index. If they have
     * both identical values and identical start indices, they will be sorted according to their end
     * indices
     * @param other The annotation to compare this to
     * @return Returns a negative int if <code>this</code> is smaller than <code>other</code>, 0 if
     *         they are equal and a positive int if <code>this</code> is larger than
     *         <code>other</code>
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    public int compareTo(final Annotation<T> other) {
        /*
         * TODO it might make more sense to give precedence to the indices (start, value, end?). Do
         * we actually need the ordering? Can we make it configurable, e.g. supplying different
         * comparators in an enum...
         */
        /* First ordering criterion: the actual value of this annotation */
        if (this.value.equals(other.getValue())) {
            /* Second ordering criterion: start index */
            if (this.start.equals(other.getStart())) {
                /* Third ordering criterion: end index */
                if (this.end.equals(other.getEnd())) {
                    return 0;
                }
                return this.end.compareTo(other.getEnd());
            }
            return this.start.compareTo(other.getStart());
        }
        /*
         * This can cause a ClassCastException if the other T is not the same as this T, which
         * happens if this was called with a casted annotation, as when calling this method from the
         * equals method, which must do the unsafe cast
         */
        return this.value.compareTo(other.getValue());
    }

    /**
     * @param other The annotation to compare with <code>this</code>
     * @return Returns true if this annotation and the other annotation annotate the same area, else
     *         false
     */
    public boolean covers(final ImmutableAnnotation<?> other) {
        return this.start.equals(other.start) && this.end.equals(other.end);
    }

    /**
     * {@inheritDoc}
     * @see com.quui.tm2.Annotation#getData()
     */
    public URL getData() {
        throw new NotImplementedException();
    }

    /**
     * {@inheritDoc}
     * @see com.quui.tm2.Annotation#author()
     */
    public Class<? extends Agent<?, ?>> author() {
        return author;
    }
}
