/*
 * 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.gen;

import com.facebook.presto.bytecode.BytecodeBlock;
import com.facebook.presto.bytecode.ClassDefinition;
import com.facebook.presto.bytecode.FieldDefinition;
import com.facebook.presto.bytecode.Variable;

import java.lang.invoke.MethodHandle;
import java.util.HashMap;
import java.util.Map;

import static com.facebook.presto.bytecode.Access.FINAL;
import static com.facebook.presto.bytecode.Access.PRIVATE;
import static com.facebook.presto.bytecode.Access.a;
import static com.facebook.presto.sql.gen.BytecodeUtils.invoke;
import static java.util.Objects.requireNonNull;

public final class CachedInstanceBinder
{
    private final ClassDefinition classDefinition;
    private final CallSiteBinder callSiteBinder;
    private final Map<FieldDefinition, MethodHandle> initializers = new HashMap<>();
    private int nextId;

    public CachedInstanceBinder(ClassDefinition classDefinition, CallSiteBinder callSiteBinder)
    {
        this.classDefinition = requireNonNull(classDefinition, "classDefinition is null");
        this.callSiteBinder = requireNonNull(callSiteBinder, "callSiteBinder is null");
    }

    public CallSiteBinder getCallSiteBinder()
    {
        return callSiteBinder;
    }

    public FieldDefinition getCachedInstance(MethodHandle methodHandle)
    {
        FieldDefinition field = classDefinition.declareField(a(PRIVATE, FINAL), "__cachedInstance" + nextId, methodHandle.type().returnType());
        initializers.put(field, methodHandle);
        nextId++;
        return field;
    }

    public void generateInitializations(Variable thisVariable, BytecodeBlock block)
    {
        for (Map.Entry<FieldDefinition, MethodHandle> entry : initializers.entrySet()) {
            Binding binding = callSiteBinder.bind(entry.getValue());
            block.append(thisVariable)
                    .append(invoke(binding, "instanceFieldConstructor"))
                    .putField(entry.getKey());
        }
    }
}
