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

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Map;
import net.ranides.assira.collection.lists.RTList;
import net.ranides.assira.collection.maps.HashMap;
import net.ranides.assira.reflection.IContext;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class RContext extends AContext {
    
    private static final FCache<IContext> CACHE = new FCache<>();

    private final AContext parent;
    
    private final RTList<TypeVariable> keys = new RTList<>();
    
    private final RTList<Type> vars = new RTList<>();
    
    private RContext(IContext parent, ParameterizedType type) {
        this.parent = (AContext)parent;
        add(type);
    }
    
    static IContext create(IContext parent, ParameterizedType type) {
        return CACHE.get(parent, type, () -> new RContext(parent, type));
    }
    
    private void add(ParameterizedType type) {
        keys.addAll(raw(type).getTypeParameters());
        vars.addAll(type.getActualTypeArguments());
        
        Type owner = type.getOwnerType();
        if(owner instanceof ParameterizedType) {
            add((ParameterizedType)owner);
        }
    }

    private static Class<?> raw(ParameterizedType type) {
        return (Class<?>)type.getRawType();
    }
    
    @Override
    protected Type resolve(TypeVariable type) {
        for(int i=0, n=keys.size(); i<n; i++) {
            if( match(keys.get(i), type) ) {
                return resolve0(vars.get(i));
            }
        }
        return parent.resolve(type);
    }
    
    private boolean match(TypeVariable key, TypeVariable type) {
        return key.getName().equals(type.getName());
    }
    
    private Type resolve0(Type type) {
        if(type instanceof TypeVariable) {
            return parent.resolve((TypeVariable)type);
        } else {
            return type;
        }
    }

    @Override
    public Map<String, Type> map() {
        HashMap<String, Type> map = new HashMap<>();
        for(int i=0, n=keys.size(); i<n; i++) {
            map.put(keys.get(i).getName(), vars.get(i));
        }
        map.putAll(super.map());
        return map;
    }
    
}
