/*
 * @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.Constructor;
import java.lang.reflect.Method;
import java.util.List;
import net.ranides.assira.collection.maps.Cache;
import static net.ranides.assira.reflection.IAttribute.*;
import net.ranides.assira.reflection.IClass;
import net.ranides.assira.reflection.IContext;
import net.ranides.assira.reflection.IMethod;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class FMethod {
    
    private static final FCache<IMethod> D_METHODS = new FCache<>();
    private static final FCache<IMethod> R_METHODS = new FCache<>();
    private static final Cache<Object, IMethod> FUNCTIONS = new Cache<>();
    
    private FMethod() {
        /* utility class */
    }

    public static IMethod newMethod(boolean declared, IContext context, Method method) {
        return (declared ? D_METHODS : R_METHODS).get(context, method, () -> new RMethod(declared, context, method, null));
    }
    
    public static IMethod newMethod(Method method) {
        IContext c = IClass.typeinfo(method.getDeclaringClass()).context();
        return FMethod.newMethod(true, c, method);
    }
    
    public static IMethod newMethod(IContext context, Constructor<?> method) {
        return D_METHODS.get(context, method, () -> new RConstructor(context, method));
    }

    public static IMethod newMethod(Constructor<?> method) {
        IContext c = IClass.typeinfo(method.getDeclaringClass()).context();
        return FMethod.newMethod(c, method);
    }
    
    public static IMethod newFunction(Object object) {
        return FUNCTIONS.get(object, () -> inewFunction(object));
    }
    
    private static IMethod inewFunction(Object object) {
        IClass<?> type = IClass.typefor(object);
        if(type.attributes().contains(SYNTHETIC)) {
            List<IMethod> methods = type.methods().require(DECLARED).discard(STATIC).list();
            if(1 == methods.size()) {
                return new LMethod(methods.get(0), object);
            }
        }
        
        int methods = type.interfaces().methods().require(ABSTRACT).stream().distinct().size();
        if(1 == methods ) {
            IMethod m = type.methods().require(DECLARED).discard(SYNTHETIC).discard(BRIDGE).first().get();
            return new RMethod(true, IContext.DEFAULT, m.reflective(), object);
        }
        
        throw new IllegalArgumentException(type + " is not lambda");
    }
    
}
