/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.facebook.presto.sql;

import com.facebook.presto.metadata.FunctionRegistry;
import com.facebook.presto.metadata.Signature;
import com.facebook.presto.operator.scalar.ScalarFunctionImplementation;
import com.facebook.presto.spi.ConnectorSession;
import com.google.common.base.Defaults;
import com.google.common.base.Throwables;

import java.lang.invoke.MethodHandle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;

public class FunctionInvoker
{
    private final FunctionRegistry registry;

    public FunctionInvoker(FunctionRegistry registry)
    {
        this.registry = requireNonNull(registry, "registry is null");
    }

    public Object invoke(Signature function, ConnectorSession session, Object... arguments)
    {
        return invoke(function, session, Arrays.asList(arguments));
    }

    /**
     * Arguments must be the native container type for the corresponding SQL types.
     *
     * Returns a value in the native container type corresponding to the declared SQL return type
     */
    public Object invoke(Signature function, ConnectorSession session, List<Object> arguments)
    {
        ScalarFunctionImplementation implementation = registry.getScalarFunctionImplementation(function);
        MethodHandle method = implementation.getMethodHandle();

        if (method.type().parameterCount() > 0 && method.type().parameterType(0) == ConnectorSession.class) {
            method = method.bindTo(session);
        }
        Iterator<Object> iterator = arguments.iterator();
        List<Object> actualArguments = new ArrayList<>();
        for (int i = 0; i < method.type().parameterCount(); i++) {
            Class<?> parameterType = method.type().parameterType(i);
            checkArgument(iterator.hasNext(), "Not enough arguments provided for method: %s", method.type());
            Object argument = iterator.next();
            if (implementation.getNullFlags().get(i)) {
                boolean isNull = argument == null;
                if (isNull) {
                    argument = Defaults.defaultValue(parameterType);
                }
                actualArguments.add(argument);
                actualArguments.add(isNull);
                // Skip the next method parameter which is marked @IsNull
                i++;
            }
            else {
                actualArguments.add(argument);
            }
        }

        checkArgument(!iterator.hasNext(), "Too many arguments provided for method: %s", method.type());

        try {
            return method.invokeWithArguments(actualArguments);
        }
        catch (Throwable throwable) {
            throw Throwables.propagate(throwable);
        }
    }
}

@@ -1003,24 +1003,20 @@
 va.util.
-Iterator
+List
 ;%0Aimport
@@ -1026,20 +1026,24 @@
 va.util.
-List
+Optional
 ;%0A%0Aimpor
@@ -1055,58 +1055,64 @@
 tic 
-com.google.common.base.Preconditions.checkArgument
+java.lang.invoke.MethodHandleProxies.asInterfaceInstance
 ;%0Aim
@@ -2018,24 +2018,157 @@
 dHandle();%0A%0A
+        // handle function on instance method, to allow use of fields%0A        method = bindInstanceFactory(method, implementation);%0A%0A
         if (
@@ -2324,66 +2324,8 @@
   %7D%0A
-        Iterator%3CObject%3E iterator = arguments.iterator();%0A
     
@@ -2410,225 +2410,267 @@
 i %3C 
-method.type().parameterCount(); i++) %7B%0A            Class%3C?%3E parameterType = method.type().parameterType(i);%0A            checkArgument(iterator.hasNext(), %22Not enough arguments provided for method: %25s%22, method.type());
+arguments.size(); i++) %7B%0A            Object argument = arguments.get(i);%0A            Optional%3CClass%3E lambdaArgument = implementation.getLambdaInterface().get(i);%0A            if (lambdaArgument.isPresent() && !MethodHandle.class.equals(lambdaArgument.get())) %7B
 %0A   
@@ -2670,38 +2670,35 @@
 ) %7B%0A            
-Object
+   
  argument = iter
@@ -2697,24 +2697,89 @@
 t = 
-iterator.next();
+asInterfaceInstance(lambdaArgument.get(), (MethodHandle) argument);%0A            %7D
 %0A   
@@ -2965,29 +2965,67 @@
 ltValue(
-parameterType
+method.type().parameterType(actualArguments.size())
 );%0A     
@@ -3133,103 +3133,8 @@
 l);%0A
-                // Skip the next method parameter which is marked @IsNull%0A                i++;%0A
     
@@ -3238,114 +3238,8 @@
  %7D%0A%0A
-        checkArgument(!iterator.hasNext(), %22Too many arguments provided for method: %25s%22, method.type());%0A%0A
     
@@ -3426,11 +3426,580 @@
  %7D%0A    %7D
+%0A%0A    private static MethodHandle bindInstanceFactory(MethodHandle method, ScalarFunctionImplementation implementation)%0A    %7B%0A        if (!implementation.getInstanceFactory().isPresent()) %7B%0A            return method;%0A        %7D%0A%0A        try %7B%0A            return method.bindTo(implementation.getInstanceFactory().get().invoke());%0A        %7D%0A        catch (Throwable throwable) %7B%0A            if (throwable instanceof InterruptedException) %7B%0A                Thread.currentThread().interrupt();%0A            %7D%0A            throw Throwables.propagate(throwable);%0A        %7D%0A    %7D
 %0A%7D%0A

Unify ExpressionInterpreter.invoke and FunctionInvoker code