/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.collection.prototype;

import lombok.EqualsAndHashCode;

import java.io.Serializable;
import java.util.function.Function;

import net.ranides.assira.reflection.IClass;

/**
 * Composite key supposed to use as a key inside any Map, but prefferrably inside GenericMap.
 * It stores both key, and allowed value type.
 *
 * It is not supposed to use directly as "inverted key" altough some classes use it
 * in such a way, because we can use it then as an non-ambiguous index identifier.
 *
 * @param <K> K
 * @param <V> V
 * @author msieron
 */
@EqualsAndHashCode(exclude = "type")
public class GenericKey<K,V> implements Function<GenericMap<K>, V>, Serializable {

    private final IClass<V> type;
    private final K id;

    @SuppressWarnings("unchecked")
    protected GenericKey(K id) {
        this.type = (IClass<V>)IClass.typefor(this).context().typeinfo("V");;
        this.id = id;
    }
    
    private GenericKey(IClass<V> type, K id) {
        this.type = type;
        this.id = id;
    }

    /**
     * Creates new composite key, which allows T values.
     *
     * @param type type
     * @param id id
     * @param <K> K
     * @param <T> T
     * @return key
     */
    public static <K, T> GenericKey<K,T> of(Class<T> type, K id) {
        return new GenericKey<>(IClass.typeinfo(type), id);
    }

    /**
     * Creates new composite key, which allows T values.
     *
     * @param type type
     * @param id id
     * @param <K> K
     * @param <T> T
     * @return key
     */
    public static <K, T> GenericKey<K, T> of(IClass<T> type, K id) {
        return new GenericKey<>(type, id);
    }

    /**
     * Creates new composite key, which allows any value.
     *
     * @param id id
     * @param <K> K
     * @return key
     */
    public static <K> GenericKey<K, Object> of(K id) {
        return new GenericKey<>(IClass.OBJECT, id);
    }

    /**
     * Returns type of values allowed to be stored inside GenericMap.
     *
     * @return type
     */
    public IClass<V> type() {
        return type;
    }

    /**
     * Returns key component
     *
     * @return K
     */
    public K id() {
        return id;
    }

    @Override
    public V apply(GenericMap<K> map) {
        return map.get(this);
    }

    @Override
    public String toString() {
        return id+":"+type.shortName();
    }
}
