/**
 * Copyright (c) André Bargull
 * Alle Rechte vorbehalten / All Rights Reserved.  Use is subject to license terms.
 *
 * <https://github.com/anba/es6draft>
 */
package com.github.anba.es6draft.compiler;

import com.github.anba.es6draft.ast.scope.Name;
import com.github.anba.es6draft.compiler.assembler.MethodName;
import com.github.anba.es6draft.compiler.assembler.Type;
import com.github.anba.es6draft.compiler.assembler.Value;
import com.github.anba.es6draft.runtime.EnvironmentRecord;

/** 
 *
 */
abstract class BindingOp<ENVREC extends EnvironmentRecord> {
    /**
     * <h1>8.1.1 Environment Records - CreateImmutableBinding(N, S)</h1>
     * <p>
     * Emit function call for: {@link EnvironmentRecord#createImmutableBinding(String, boolean)}
     * <p>
     * stack: [] {@literal ->} []
     * 
     * @param envRec
     *            the environment record
     * @param name
     *            the binding name
     * @param strict
     *            {@code true} for strict immutable bindings
     * @param mv
     *            the instruction visitor
     */
    abstract void createImmutableBinding(Value<? extends ENVREC> envRec, Name name, boolean strict,
            InstructionVisitor mv);

    /**
     * <h1>8.1.1 Environment Records - CreateMutableBinding(N, D)</h1>
     * <p>
     * Emit function call for: {@link EnvironmentRecord#createMutableBinding(String, boolean)}
     * <p>
     * stack: [] {@literal ->} []
     * 
     * @param envRec
     *            the environment record
     * @param name
     *            the binding name
     * @param deletable
     *            {@code true} for deletable mutable bindings
     * @param mv
     *            the instruction visitor
     */
    abstract void createMutableBinding(Value<? extends ENVREC> envRec, Name name, boolean deletable,
            InstructionVisitor mv);

    /**
     * <h1>8.1.1 Environment Records - InitializeBinding(N, V)</h1>
     * <p>
     * Emit function call for: {@link EnvironmentRecord#initializeBinding(String, Object)}
     * <p>
     * stack: [] {@literal ->} []
     * 
     * @param envRec
     *            the environment record
     * @param name
     *            the binding name
     * @param value
     *            the initial binding value
     * @param mv
     *            the instruction visitor
     */
    abstract void initializeBinding(Value<? extends ENVREC> envRec, Name name, Value<?> value, InstructionVisitor mv);

    /**
     * <h1>8.1.1 Environment Records - HasBinding(N)</h1>
     * <p>
     * Emit function call for: {@link EnvironmentRecord#hasBinding(String)}
     * <p>
     * stack: [] {@literal ->} [boolean]
     * 
     * @param envRec
     *            the environment record
     * @param name
     *            the binding name
     * @param mv
     *            the instruction visitor
     */
    abstract void hasBinding(Value<? extends ENVREC> envRec, Name name, InstructionVisitor mv);

    /**
     * <h1>8.1.1 Environment Records - GetBindingValue(N,S)</h1>
     * <p>
     * Emit function call for: {@link EnvironmentRecord#getBindingValue(String, boolean)}
     * <p>
     * stack: [] {@literal ->} [{@literal <value>}]
     * 
     * @param envRec
     *            the environment record
     * @param name
     *            the binding name
     * @param strict
     *            {@code true} if in strict-mode code
     * @param mv
     *            the instruction visitor
     */
    abstract void getBindingValue(Value<? extends ENVREC> envRec, Name name, boolean strict, InstructionVisitor mv);

    /**
     * <h1>8.1.1 Environment Records - SetMutableBinding(N, V, S)</h1>
     * <p>
     * Emit function call for: {@link EnvironmentRecord#setMutableBinding(String, Object, boolean)}
     * <p>
     * stack: [] {@literal ->} []
     * 
     * @param envRec
     *            the environment record
     * @param name
     *            the binding name
     * @param value
     *            the binding value
     * @param strict
     *            {@code true} if in strict-mode code
     * @param mv
     *            the instruction visitor
     */
    abstract void setMutableBinding(Value<? extends ENVREC> envRec, Name name, Value<?> value, boolean strict,
            InstructionVisitor mv);

    /**
     * Returns the {@code BindingOp} implementation for the binding name.
     * 
     * @param envRec
     *            the environment record
     * @param name
     *            the binding name
     * @return the {@code BindingOp}
     */
    @SuppressWarnings("unchecked")
    static <ENV extends EnvironmentRecord, E extends ENV> BindingOp<ENV> of(Value<E> envRec, Name name) {
        return (BindingOp<ENV>) LOOKUP;
    }

    private static final class Methods {
        // class: EnvironmentRecord
        static final MethodName EnvironmentRecord_hasBinding = MethodName.findInterface(Types.EnvironmentRecord,
                "hasBinding", Type.methodType(Type.BOOLEAN_TYPE, Types.String));

        static final MethodName EnvironmentRecord_createMutableBinding = MethodName.findInterface(
                Types.EnvironmentRecord, "createMutableBinding",
                Type.methodType(Type.VOID_TYPE, Types.String, Type.BOOLEAN_TYPE));

        static final MethodName EnvironmentRecord_createImmutableBinding = MethodName.findInterface(
                Types.EnvironmentRecord, "createImmutableBinding",
                Type.methodType(Type.VOID_TYPE, Types.String, Type.BOOLEAN_TYPE));

        static final MethodName EnvironmentRecord_initializeBinding = MethodName.findInterface(Types.EnvironmentRecord,
                "initializeBinding", Type.methodType(Type.VOID_TYPE, Types.String, Types.Object));

        static final MethodName EnvironmentRecord_getBindingValue = MethodName.findInterface(Types.EnvironmentRecord,
                "getBindingValue", Type.methodType(Types.Object, Types.String, Type.BOOLEAN_TYPE));

        static final MethodName EnvironmentRecord_setMutableBinding = MethodName.findInterface(Types.EnvironmentRecord,
                "setMutableBinding", Type.methodType(Type.VOID_TYPE, Types.String, Types.Object, Type.BOOLEAN_TYPE));
    }

    static final BindingOp<EnvironmentRecord> LOOKUP = new BindingOp<EnvironmentRecord>() {
        @Override
        void createImmutableBinding(Value<? extends EnvironmentRecord> envRec, Name name, boolean strict,
                InstructionVisitor mv) {
            mv.load(envRec);
            mv.aconst(name.getIdentifier());
            mv.iconst(strict);
            mv.invoke(Methods.EnvironmentRecord_createImmutableBinding);
        }

        @Override
        void createMutableBinding(Value<? extends EnvironmentRecord> envRec, Name name, boolean deletable,
                InstructionVisitor mv) {
            mv.load(envRec);
            mv.aconst(name.getIdentifier());
            mv.iconst(deletable);
            mv.invoke(Methods.EnvironmentRecord_createMutableBinding);
        }

        @Override
        void initializeBinding(Value<? extends EnvironmentRecord> envRec, Name name, Value<?> value,
                InstructionVisitor mv) {
            mv.load(envRec);
            mv.aconst(name.getIdentifier());
            mv.load(value);
            mv.invoke(Methods.EnvironmentRecord_initializeBinding);
        }

        @Override
        void hasBinding(Value<? extends EnvironmentRecord> envRec, Name name, InstructionVisitor mv) {
            mv.load(envRec);
            mv.aconst(name.getIdentifier());
            mv.invoke(Methods.EnvironmentRecord_hasBinding);
        }

        @Override
        void getBindingValue(Value<? extends EnvironmentRecord> envRec, Name name, boolean strict,
                InstructionVisitor mv) {
            mv.load(envRec);
            mv.aconst(name.getIdentifier());
            mv.iconst(strict);
            mv.invoke(Methods.EnvironmentRecord_getBindingValue);
        }

        @Override
        void setMutableBinding(Value<? extends EnvironmentRecord> envRec, Name name, Value<?> value, boolean strict,
                InstructionVisitor mv) {
            mv.load(envRec);
            mv.aconst(name.getIdentifier());
            mv.load(value);
            mv.iconst(strict);
            mv.invoke(Methods.EnvironmentRecord_setMutableBinding);
        }
    };
}
