/*
 *  @author Ranides Atterwim <ranides@gmail.com>
 *  @copyright Ranides Atterwim
 *  @license WTFPL
 *  @url http://ranides.net/projects/assira
 */

package net.ranides.assira.generic;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import net.ranides.assira.reflection.GenericClass;
import net.ranides.assira.reflection.TypeFactory;
import net.ranides.assira.reflection.util.ReflectFormat;

/**
 * Klasa przekazująca dalej informację o klasie w genericu, jeśli nie mamy obiektu
 * {@code Class<T>} możemy opakować T właśnie w tę klasę. Prosty przykład przypadku,
 * który będzie stwarzał problem:
 * <blockquote><pre>
 * List&lt;T&gt; makeList(Class&lt;T&gt; clazz, int size) {
 *     return new ArrayList&lt;T&gt;(size);
 * }
 *
 * List&lt;T&gt; copyList(List&lt;T&gt; source) {
 *     List&lt;T&gt; target = makeList( ? , source.size());
 *     for(T item : source) target.add(item);
 *     return target;
 * }
 * </pre></blockquote>
 *
 * {@code makeList} pobiera instancję {@code Class<T>} bo jakoś musi dostać to nieszczęsne T.
 * Problem w tym, że copyList już takiej instancji nie dostaje - a nie da się utworzyć {@code new Class<T>()}
 * żeby jej przekazać. Oczywiście {@code new TypeToken<T>()} jest legalne. I tylko z tego
 * właśnie powodu klasa została napisana. Rozszerzony i poprawiony kod:
 *
 * <blockquote><pre>
 * List&lt;T&gt; makeList(Class&lt;T&gt; clazz, int size) {
 *     return new ArrayList&lt;T&gt;(size);
 * }
 *
 * List&lt;T&gt; makeList(TypeToken&lt;T&gt; token, int size) {
 *     return new ArrayList&lt;T&gt;(size);
 * }
 *
 * List&lt;T&gt; copyList(List&lt;T&gt; source) {
 *     List&lt;T&gt; target = makeList( new TypeToken&lt;T&gt;(){} , source.size());
 *     for(T item : source) target.add(item);
 *     return target;
 * }
 * </pre></blockquote>
 *
 * <p>
 * Uwaga. Klasa rozszerzona, potrafi porządnie wydedukować instancję {@code Class<T>}.
 * Rozszerzenie podpatrzone z biblioteki <a href="http://wiki.fasterxml.com/JacksonHome">Jackson JSON Processor</a>
 * </p>
 * @param <T> typ, który chcemy przekazać dalej
 * @author ranides
 */
public abstract class TypeToken<T> implements Comparable<TypeToken<T>>, GenericClass {
    
    private final Type type;

    /**
     * Tworzy nową instancję klasy TypeToken. Możliwe i sensowne tylko w formie
     * utworzenia nowej klasy anonimowej, parametryzowanej typem, który chcemy
     * przekazać / przechować / wydedukować:
     * <pre>
     * {@code new TypeToken<Integer>(){ } }
     * </pre>
     *
     */
    protected TypeToken()
    {
        Type superClass = getClass().getGenericSuperclass();
        if (superClass instanceof Class<?>) { // sanity check, should never happen
            throw new IllegalArgumentException("Internal error: TypeReference constructed without actual type information");
        }
        /* 22-Dec-2008, tatu: Not sure if this case is safe -- I suspect
         *   it is possible to make it fail?
         *   But let's deal with specific
         *   case when we know an actual use case, and thereby suitable
         *   workarounds for valid case(s) and/or error to throw
         *   on invalid one(s).
         */
        type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
    }
	
	protected TypeToken(Class<?> type) {
		this.type = type;
	}

    /**
     * Zwraca wydedukowany typ parametru. Bez dodatkowego wsparcia narzędzi
     * wyodrębniających i przetwarzających otrzymaną informację niezbyt użyteczny.
     * @return
     */
    @Override
    public Type type() {
        return type;
    }

    /**
     * The only reason we define this method (and require implementation
     * of <code>Comparable</code>) is to prevent constructing a
     * reference without type information.
     */
    @Override
    public int compareTo(TypeToken<T> object) {
        // just need an implementation, not a good one... hence:
        return 0;
    }

    @Override
    public int hashCode() {
        // just need an implementation, not a good one... hence:
        return 0;
    }

    @Override
    public boolean equals(Object obj) {
        // just need an implementation, not a good one... hence:
        return obj!=null && getClass().equals(obj.getClass());
    }

    @Override
    public String name() {
        return rawType().getSimpleName();
    }

    @Override
    public Class<?> rawType() {
        if(type instanceof ParameterizedType) {
            return (Class)((ParameterizedType)type).getRawType();
        } else {
            return (Class)type;
        }
    }

    @Override
    public GenericClass[] params() {
        if( type instanceof ParameterizedType ) {
            ParameterizedType ptype = (ParameterizedType)type;
            return TypeFactory.construct(ptype.getActualTypeArguments());
        }
        return TypeFactory.EMPTY_CLASSES;
    }

    @Override
    public boolean isParametrised() {
        return type instanceof ParameterizedType;
    }

    @Override
    public boolean isResolved() {
        return true;
    }

    @Override
    public String toString() {
        return ReflectFormat.signature(this);
    }

    /**
     * To przejdzie tylko wtedy, gdy wywołamy na rzecz klasy, która jest subklasą genericsa
     * @param clazz
     * @return
     */
    public static Class<?>[] getTypeArguments(Class<?> clazz) {
        Type[] types = ((ParameterizedType)clazz.getGenericSuperclass()).getActualTypeArguments();
        Class<?>[] result = new Class<?>[types.length];
        for(int i=0; i<types.length; i++) { result[i] = (Class<?>)types[i]; }
        return result;
    }

}
