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

import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.reflection.*;

import java.io.Serializable;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Collections;
import java.util.Map;

import static net.ranides.assira.reflection.impl.AClass.NULL;

/**
 * Base class for all IContext implementations.
 * Implements default methods, delegates some work to internal classes:
 * RContext, FElements, AClass
 *
 * Defines protected methods which will be overridden by descendants.
 *
 * By default, it does not resolve any type arguments, because it has no
 * information inside about collected types.
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public class AContext implements IContext, Serializable {

    private static final long serialVersionUID = 1L;

    private static final IContext DEFAULT = new AContext();

    protected AContext() {
        // do nothing
    }

    /**
     * Internal method, used by {@link IContext#getDefault()}
     *
     * @return IContext
     */
    public static IContext $default() {
        return DEFAULT;
    }

    @Override
    public final IContext derive(Type type) {
        if(type!=null && type!=Object.class && !derived(type)) {
            return RContext.create(this, type);
        } else {
            return this;
        }
    }

    @Override
    public boolean derived(Type type) {
        return false;
    }

    @Override
    public final IClasses typesinfo(Type... types) {
        return FElements.newClasses(IHints.none(), CQuery.from().array(types).map(this::typeinfo));
    }
    
    @Override
    public final IClasses typesfor(Object... objects) {
        return FElements.newClasses(IHints.none(), CQuery.from().array(objects).map(this::typefor));
    }

    @SuppressWarnings("unchecked")
    @Override
    public final <R> IClass<R> typefor(R object) {
        return (null == object) ? IClass.NULL : itypeinfo(object.getClass());
    }

    @SuppressWarnings("unchecked")
    @Override
    public <R> IClass<R> typeinfo(Class<R> type) {
        return itypeinfo(type);
    }
    
    @Override
    public final IClass<?> typeinfo(Type type) {
        return itypeinfo(type);
    }

    @Override
    public IClass<?> typeinfo(String type) {
        return resolveNS(type);
    }

    @SuppressWarnings("rawtypes")
    private IClass itypeinfo(Type type) {
        if(type instanceof Class<?>) {
            return FClass.newClass(this, (Class<?>)type);
        }
        if(type instanceof ParameterizedType) {
            ParameterizedType ptype = (ParameterizedType)type;
            return FClass.newClass(this, (Class<?>)ptype.getRawType(), ptype);
        }
        if(type instanceof GenericArrayType) {
            return FClass.newClass(this, (GenericArrayType)type);
        }
        if(type instanceof WildcardType) {
            return FClass.newClass(this, (WildcardType)type);
        }
        if(type instanceof TypeVariable) {
            return resolveTV((TypeVariable<?>)type);
        }
        return NULL;
    }

    protected IClass<?> resolveNS(String type) {
        Type rtype = resolve(type);
        return rtype != null ? typeinfo(rtype) : null;
    }

    protected IClass<?> resolveTV(TypeVariable<?> type) {
        Type rtype = resolve(type);
        // sprawdzamy czy udało się znaleźć jakiś inny typ, jeśli wynikiem jest podany argument,
        // to znaczy, że nic już nie możemy mądrego zrobić: zwróćmy wynik
        if(rtype != type) { //NOPMD
            return typeinfo(rtype);
        } else {
            return FClass.newClass(this, type);
        }
    }

    protected Type resolve(String type) {
        return null;
    }

    protected Type resolve(TypeVariable<?> type) {
        return type;
    }

    @Override
    public Map<String, Type> map() {
        return Collections.emptyMap();
    }
    
}
