package com.github.rschmitt.dynamicobject.internal;

import static java.lang.invoke.MethodType.methodType;

import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import com.github.rschmitt.dynamicobject.DynamicObject;
import net.fushizen.invokedynamic.proxy.DynamicInvocationHandler;

public class InvokeDynamicInvocationHandler implements DynamicInvocationHandler {
    private static final ConcurrentMap<Class, MethodHandle> validateMethodHandleCache = new ConcurrentHashMap<>();

    private final Class dynamicObjectType;

    public InvokeDynamicInvocationHandler(Class dynamicObjectType) {
        this.dynamicObjectType = dynamicObjectType;
    }

    @Override
    @SuppressWarnings("unchecked")
    public CallSite handleInvocation(
            MethodHandles.Lookup lookup,
            String methodName,
            MethodType methodType,
            MethodHandle superMethod
    ) throws Throwable {
        Class proxyType = methodType.parameterArray()[0];
        MethodHandle mh;
        if (superMethod != null) {
            mh = superMethod.asType(methodType);
            if ("validate".equals(methodName)) {
                if (mh.type().returnType().equals(dynamicObjectType))
                    return new ConstantCallSite(mh);
                else
                    validateMethodHandleCache.put(dynamicObjectType, mh);
            } else return new ConstantCallSite(mh);
        }
        if ("getMap".equals(methodName)) {
            mh = lookup.findSpecial(DynamicObjectInstance.class, "getMap", methodType(Map.class), proxyType).asType(methodType);
        } else if ("getType".equals(methodName)) {
            mh = lookup.findSpecial(DynamicObjectInstance.class, "getType", methodType(Class.class), proxyType).asType(methodType);
        } else if ("toString".equals(methodName)) {
            mh = lookup.findSpecial(DynamicObjectInstance.class, "toString", methodType(String.class), proxyType).asType(methodType);
        } else if ("hashCode".equals(methodName)) {
            mh = lookup.findSpecial(DynamicObjectInstance.class, "toString", methodType(int.class), proxyType).asType(methodType);
        } else if ("prettyPrint".equals(methodName)) {
            mh = lookup.findSpecial(DynamicObjectInstance.class, "pprint", methodType(Void.TYPE), proxyType).asType(methodType);
        } else if ("toFormattedString".equals(methodName)) {
            mh = lookup.findSpecial(DynamicObjectInstance.class, "toFormattedString", methodType(String.class), proxyType).asType(methodType);
        } else if ("merge".equals(methodName)) {
            mh = lookup.findSpecial(DynamicObjectInstance.class, "merge", methodType(Object.class, Object.class), proxyType).asType(methodType);
        } else if ("intersect".equals(methodName)) {
            mh = lookup.findSpecial(DynamicObjectInstance.class, "intersect", methodType(Object.class, Object.class), proxyType).asType(methodType);
        } else if ("subtract".equals(methodName)) {
            mh = lookup.findSpecial(DynamicObjectInstance.class, "subtract", methodType(Object.class, Object.class), proxyType).asType(methodType);
        } else if ("validate".equals(methodName)) {
            mh = lookup.findSpecial(DynamicObjectInstance.class, "$$validate", methodType(Object.class), proxyType).asType(methodType);
        } else if ("$$customValidate".equals(methodName)) {
            mh = validateMethodHandleCache.getOrDefault(
                    dynamicObjectType,
                    lookup.findSpecial(DynamicObjectInstance.class, "$$noop", methodType(Object.class), proxyType).asType(methodType)
            );
        } else if ("equals".equals(methodName)) {
            mh = lookup.findSpecial(DynamicObjectInstance.class, "equals", methodType(boolean.class, Object.class), proxyType).asType(methodType);
        } else {
            Method method = dynamicObjectType.getMethod(methodName, methodType.dropParameterTypes(0, 1).parameterArray());

            if (isBuilderMethod(method)) {
                Object key = Reflection.getKeyForBuilder(method);
                if (Reflection.isMetadataBuilder(method)) {
                    mh = lookup.findSpecial(DynamicObjectInstance.class, "assocMeta", methodType(DynamicObject.class, Object.class, Object.class), proxyType);
                    mh = MethodHandles.insertArguments(mh, 1, key);
                    mh = mh.asType(methodType);
                } else {
                    mh = lookup.findSpecial(DynamicObjectInstance.class, "convertAndAssoc", methodType(DynamicObject.class, Object.class, Object.class), proxyType);
                    mh = MethodHandles.insertArguments(mh, 1, key);
                    mh = mh.asType(methodType);
                }
            } else {
                Object key = Reflection.getKeyForGetter(method);
                if (Reflection.isMetadataGetter(method)) {
                    mh = lookup.findSpecial(DynamicObjectInstance.class, "getMetadataFor", methodType(Object.class, Object.class), proxyType);
                    mh = MethodHandles.insertArguments(mh, 1, key);
                    mh = mh.asType(methodType);
                } else {
                    boolean isRequired = Reflection.isRequired(method);
                    Type genericReturnType = method.getGenericReturnType();
                    mh = lookup.findSpecial(DynamicObjectInstance.class, "invokeGetter", methodType(Object.class, Object.class, boolean.class, Type.class), proxyType);
                    mh = MethodHandles.insertArguments(mh, 1, key, isRequired, genericReturnType);
                    mh = mh.asType(methodType);
                }
            }
        }
        return new ConstantCallSite(mh);
    }

    private boolean isBuilderMethod(Method method) {
        return method.getReturnType().equals(dynamicObjectType) && method.getParameterCount() == 1;
    }
}

@@ -358,30 +358,8 @@
 pe;%0A
-import java.util.Map;%0A
 impo
@@ -401,16 +401,16 @@
 ashMap;%0A
+
 import j
@@ -1707,1723 +1707,8 @@
 f (%22
-getMap%22.equals(methodName)) %7B%0A            mh = lookup.findSpecial(DynamicObjectInstance.class, %22getMap%22, methodType(Map.class), proxyType).asType(methodType);%0A        %7D else if (%22getType%22.equals(methodName)) %7B%0A            mh = lookup.findSpecial(DynamicObjectInstance.class, %22getType%22, methodType(Class.class), proxyType).asType(methodType);%0A        %7D else if (%22toString%22.equals(methodName)) %7B%0A            mh = lookup.findSpecial(DynamicObjectInstance.class, %22toString%22, methodType(String.class), proxyType).asType(methodType);%0A        %7D else if (%22hashCode%22.equals(methodName)) %7B%0A            mh = lookup.findSpecial(DynamicObjectInstance.class, %22toString%22, methodType(int.class), proxyType).asType(methodType);%0A        %7D else if (%22prettyPrint%22.equals(methodName)) %7B%0A            mh = lookup.findSpecial(DynamicObjectInstance.class, %22pprint%22, methodType(Void.TYPE), proxyType).asType(methodType);%0A        %7D else if (%22toFormattedString%22.equals(methodName)) %7B%0A            mh = lookup.findSpecial(DynamicObjectInstance.class, %22toFormattedString%22, methodType(String.class), proxyType).asType(methodType);%0A        %7D else if (%22merge%22.equals(methodName)) %7B%0A            mh = lookup.findSpecial(DynamicObjectInstance.class, %22merge%22, methodType(Object.class, Object.class), proxyType).asType(methodType);%0A        %7D else if (%22intersect%22.equals(methodName)) %7B%0A            mh = lookup.findSpecial(DynamicObjectInstance.class, %22intersect%22, methodType(Object.class, Object.class), proxyType).asType(methodType);%0A        %7D else if (%22subtract%22.equals(methodName)) %7B%0A            mh = lookup.findSpecial(DynamicObjectInstance.class, %22subtract%22, methodType(Object.class, Object.class), proxyType).asType(methodType);%0A        %7D else if (%22
 vali
@@ -1829,32 +1829,48 @@
 ype(Object.class
+, new Class%5B%5D%7B %7D
 ), proxyType).as
@@ -2177,16 +2177,16 @@
 odType)%0A
+
         
@@ -2196,205 +2196,8 @@
  );%0A
-        %7D else if (%22equals%22.equals(methodName)) %7B%0A            mh = lookup.findSpecial(DynamicObjectInstance.class, %22equals%22, methodType(boolean.class, Object.class), proxyType).asType(methodType);%0A
     

Refactor InvokeDynamicInvocationHandler