package org.tensorics.core.tensorbacked;

import org.tensorics.core.tensor.Tensor;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static java.util.Objects.requireNonNull;

public final class ProxiedInterfaceTensorbackeds {

    private ProxiedInterfaceTensorbackeds() {
        /* only static methods*/
    }

    public static final <V, T extends Tensorbacked<V>> T create(Class<T> tensorbackedType, Tensor<V> tensor) {
        if (!tensorbackedType.isInterface()) {
            throw new IllegalArgumentException("The given tensorbacked type '" + tensorbackedType + "' is not an interface! Therefore, it cannot be instantiated by proxying!");
        }
        return (T) Proxy.newProxyInstance(Tensorbacked.class.getClassLoader(), new Class<?>[]{tensorbackedType}, new DelegatingInvocationHandler<>(tensor));
    }

    private final static class DelegatingInvocationHandler<V> implements InvocationHandler {
        private final Tensor<V> delegate;

        public DelegatingInvocationHandler(Tensor<V> delegate) {
            this.delegate = requireNonNull(delegate, "delegate must not be null");
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            if (method.isDefault()) {
                MethodCallHandler callHandler = DefaultMethodCallHandler.forMethod(method);
                return callHandler.invoke(proxy, args);
            }

            if (Tensorbacked.class.equals(method.getDeclaringClass())
                    && "tensor".equals(method.getName())
                    && method.getParameterCount() == 0
                    && method.getReturnType().isAssignableFrom(Tensor.class)) {
                return delegate;
            }

            throw new UnsupportedOperationException("The method " + method + " is not supported by the proxy!");
        }
    }


    @FunctionalInterface
    private interface MethodCallHandler {
        Object invoke(Object proxy, Object[] args) throws Throwable;
    }


    @FunctionalInterface
    private interface MethodInterpreter extends InvocationHandler {

        @Override
        default Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            MethodCallHandler handler = interpret(method);
            return handler.invoke(proxy, args);
        }

        MethodCallHandler interpret(Method method);
    }

    private static class DefaultMethodCallHandler {

        private DefaultMethodCallHandler() {
        }

        private static final ConcurrentMap<Method, MethodCallHandler> cache = new ConcurrentHashMap<>();

        private static MethodCallHandler forMethod(Method method) {
            return cache.computeIfAbsent(method, m -> {
                MethodHandle handle = getMethodHandle(m);
                return (proxy, args) -> handle.bindTo(proxy).invokeWithArguments(args);
            });
        }

        private static MethodHandle getMethodHandle(Method method) {
            Class<?> declaringClass = method.getDeclaringClass();

            try {
                Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                        .getDeclaredConstructor(Class.class, int.class);
                constructor.setAccessible(true);

                return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
                        .unreflectSpecial(method, declaringClass);
            } catch (IllegalAccessException | NoSuchMethodException |
                    InstantiationException | InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        }
    }

}

@@ -154,16 +154,52 @@
 andles;%0A
+import java.lang.invoke.MethodType;%0A
 import j
@@ -486,16 +486,17 @@
  methods
+ 
 */%0A    %7D
@@ -515,22 +515,16 @@
  static 
-final 
 %3CV, T ex
@@ -746,16 +746,36 @@
 ckedType
+%0A                   
  + %22' is
@@ -949,17 +949,19 @@
 ass%3C?%3E%5B%5D
-%7B
+ %7B 
 tensorba
@@ -972,10 +972,27 @@
 Type
+ 
 %7D,
+%0A               
  new
@@ -1028,16 +1028,34 @@
 %3E(tensor
+, tensorbackedType
 ));%0A    
@@ -1117,16 +1117,43 @@
 andler%3CV
+, T extends Tensorbacked%3CV%3E
 %3E implem
@@ -1218,16 +1218,54 @@
 elegate;
+%0A        private final Class%3CT%3E intfc;
 %0A%0A      
@@ -1319,16 +1319,32 @@
 delegate
+, Class%3CT%3E intfc
 ) %7B%0A    
@@ -1418,24 +1418,102 @@
  be null%22);%0A
+            this.intfc = requireNonNull(intfc, %22interface must not be null%22);%0A
         %7D%0A%0A 
@@ -1680,140 +1680,281 @@
     
+return 
 Method
-Call
 Handle
-r callHandler = DefaultMethodCallHandler.forMethod(method);%0A                return callHandler.invoke(proxy, 
+s.lookup()%0A                        .findSpecial(intfc, method.getName(),%0A                                MethodType.methodType(method.getReturnType(), method.getParameterTypes()), intfc)%0A                        .bindTo(proxy).invokeWithArguments(
 args);%0A
+%0A
     
@@ -2033,36 +2033,16 @@
 Class())
-%0A                   
  && %22ten
@@ -2086,32 +2086,33 @@
              && 
+(
 method.getParame
@@ -2126,36 +2126,17 @@
 t() == 0
-%0A                   
+)
  && meth
@@ -2358,33 +2358,32 @@
        %7D%0A    %7D%0A%0A
-%0A
     @FunctionalI
@@ -2509,17 +2509,16 @@
 %0A    %7D%0A%0A
-%0A
     @Fun
@@ -3867,33 +3867,8 @@
 ATE)
-%0A                        
 .unr
@@ -3888,16 +3888,40 @@
 (method,
+%0A                       
  declari
@@ -3999,36 +3999,16 @@
 eption %7C
-%0A                   
  Instant
@@ -4018,24 +4018,44 @@
 ionException
+%0A                   
  %7C Invocatio

fix for proxied interfaces