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

import static com.github.anba.es6draft.runtime.AbstractOperations.Set;
import static com.github.anba.es6draft.runtime.AbstractOperations.ToObject;
import static com.github.anba.es6draft.runtime.AbstractOperations.ToString;
import static com.github.anba.es6draft.runtime.internal.Errors.newReferenceError;
import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError;

import com.github.anba.es6draft.runtime.DeclarativeEnvironmentRecord;
import com.github.anba.es6draft.runtime.EnvironmentRecord;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.internal.Messages;
import com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject;

/**
 * <h1>6 ECMAScript Data Types and Values</h1><br>
 * <h2>6.2 ECMAScript Specification Types</h2>
 * <ul>
 * <li>6.2.3 The Reference Specification Type
 * </ul>
 */
public abstract class Reference<BASE, NAME> {
    private Reference() {
    }

    /**
     * GetBase(V)
     * 
     * @return the reference base
     */
    public abstract BASE getBase();

    /**
     * GetReferencedName(V)
     * 
     * @return the reference name
     */
    public abstract NAME getReferencedName();

    /**
     * IsStrictReference(V)
     * 
     * @return {@code true} if is strict mode reference
     */
    public abstract boolean isStrictReference();

    /**
     * HasPrimitiveBase(V)
     * 
     * @return {@code true} if the base is a primitive value
     */
    public abstract boolean hasPrimitiveBase();

    /**
     * IsPropertyReference(V)
     * 
     * @return {@code true} if this is a property reference
     */
    public abstract boolean isPropertyReference();

    /**
     * IsUnresolvableReference(V)
     * 
     * @return {@code true} if the reference is unresolvable
     */
    public abstract boolean isUnresolvableReference();

    /**
     * IsSuperReference(V)
     * 
     * @return {@code true} if this is a super reference
     */
    public abstract boolean isSuperReference();

    /**
     * [6.2.3.1] GetValue (V)
     * 
     * @param cx
     *            the execution context
     * @return the reference value
     */
    public abstract Object getValue(ExecutionContext cx);

    /**
     * [6.2.3.2] PutValue (V, W)
     * 
     * @param w
     *            the new reference value
     * @param cx
     *            the execution context
     */
    public abstract void putValue(Object w, ExecutionContext cx);

    /**
     * [6.2.3.3] GetThisValue (V)
     * 
     * @return the reference this value
     */
    public abstract Object getThisValue();

    /**
     * [6.2.3.4] InitializeReferencedBinding (V, W)
     * 
     * 
     * @param w
     *            the new reference value
     */
    public abstract void initializeReferencedBinding(Object w);

    /**
     * 12.5.4 The delete Operator<br>
     * Runtime Semantics: Evaluation 12.5.4.2
     * 
     * @param cx
     *            the execution context
     * @return {@code true} on success
     */
    public abstract boolean delete(ExecutionContext cx);

    /**
     * [6.2.3.1] GetValue (V)
     * 
     * @param v
     *            the reference
     * @param cx
     *            the execution context
     * @return the reference value
     */
    public static Object GetValue(Object v, ExecutionContext cx) {
        /* step 1 (not applicable) */
        /* step 2 */
        if (!(v instanceof Reference))
            return v;
        /* steps 3-6 */
        return ((Reference<?, ?>) v).getValue(cx);
    }

    /**
     * [6.2.3.1] GetValue (V)
     * 
     * @param v
     *            the reference
     * @param cx
     *            the execution context
     * @return the reference value
     */
    public static Object GetValue(Reference<?, ?> v, ExecutionContext cx) {
        /* steps 1-2 (not applicable) */
        /* steps 3-6 */
        return v.getValue(cx);
    }

    /**
     * [6.2.3.2] PutValue (V, W)
     * 
     * @param v
     *            the reference
     * @param w
     *            the new reference value
     * @param cx
     *            the execution context
     */
    public static void PutValue(Object v, Object w, ExecutionContext cx) {
        /* steps 1-2 (not applicable) */
        /* step 3 */
        if (!(v instanceof Reference)) {
            throw newReferenceError(cx, Messages.Key.InvalidReference);
        }
        /* steps 4-8 */
        ((Reference<?, ?>) v).putValue(w, cx);
    }

    /**
     * [6.2.3.2] PutValue (V, W)
     * 
     * @param v
     *            the reference
     * @param w
     *            the new reference value
     * @param cx
     *            the execution context
     */
    public static void PutValue(Reference<?, ?> v, Object w, ExecutionContext cx) {
        /* steps 1-3 (not applicable) */
        /* steps 4-8 */
        v.putValue(w, cx);
    }

    /**
     * [6.2.3.3] GetThisValue (V)
     * 
     * @param v
     *            the reference
     * @param cx
     *            the execution context
     * @return the reference this value
     */
    public static Object GetThisValue(ExecutionContext cx, Object v) {
        /* step 1 */
        assert v instanceof Reference && ((Reference<?, ?>) v).isPropertyReference();
        /* steps 2-3 */
        return ((Reference<?, ?>) v).getThisValue();
    }

    /**
     * Reference specialization for binding references.
     */
    public static final class BindingReference extends Reference<DeclarativeEnvironmentRecord, String> {
        private final DeclarativeEnvironmentRecord base;
        private final DeclarativeEnvironmentRecord.Binding binding;
        private final String referencedName;
        private final boolean strictReference;

        public BindingReference(DeclarativeEnvironmentRecord base, DeclarativeEnvironmentRecord.Binding binding,
                String referencedName, boolean strictReference) {
            this.base = base;
            this.binding = binding;
            this.referencedName = referencedName;
            this.strictReference = strictReference;
        }

        @Override
        public DeclarativeEnvironmentRecord getBase() {
            return base;
        }

        @Override
        public String getReferencedName() {
            return referencedName;
        }

        @Override
        public boolean isStrictReference() {
            return strictReference;
        }

        @Override
        public boolean hasPrimitiveBase() {
            return false;
        }

        @Override
        public boolean isPropertyReference() {
            return false;
        }

        @Override
        public boolean isUnresolvableReference() {
            return false;
        }

        @Override
        public boolean isSuperReference() {
            return false;
        }

        @Override
        public Object getValue(ExecutionContext cx) {
            if (!binding.isInitialized()) {
                throw newReferenceError(cx, Messages.Key.UninitializedBinding, referencedName);
            }
            return binding.getValue();
        }

        @Override
        public void putValue(Object w, ExecutionContext cx) {
            if (!binding.isInitialized()) {
                throw newReferenceError(cx, Messages.Key.UninitializedBinding, referencedName);
            } else if (binding.isMutable()) {
                binding.setValue(w);
            } else if (strictReference || binding.isStrict()) {
                throw newTypeError(cx, Messages.Key.ImmutableBinding, referencedName);
            }
        }

        @Override
        public boolean delete(ExecutionContext cx) {
            return false;
        }

        @Override
        public Object getThisValue() {
            throw new AssertionError();
        }

        @Override
        public void initializeReferencedBinding(Object w) {
            binding.initialize(w);
        }
    }

    /**
     * Reference specialization for identifier references.
     */
    public static final class IdentifierReference<RECORD extends EnvironmentRecord> extends Reference<RECORD, String> {
        private final RECORD base;
        private final String referencedName;
        private final boolean strictReference;

        public IdentifierReference(RECORD base, String referencedName, boolean strictReference) {
            this.base = base;
            this.referencedName = referencedName;
            this.strictReference = strictReference;
        }

        @Override
        public RECORD getBase() {
            return base;
        }

        @Override
        public String getReferencedName() {
            return referencedName;
        }

        @Override
        public boolean isStrictReference() {
            return strictReference;
        }

        @Override
        public boolean hasPrimitiveBase() {
            return false;
        }

        @Override
        public boolean isPropertyReference() {
            return false;
        }

        @Override
        public boolean isUnresolvableReference() {
            return false;
        }

        @Override
        public boolean isSuperReference() {
            return false;
        }

        @Override
        public Object getValue(ExecutionContext cx) {
            /* steps 4-5 (not applicable) */
            /* steps 3, 6 */
            return getBase().getBindingValue(getReferencedName(), isStrictReference());
        }

        @Override
        public void putValue(Object w, ExecutionContext cx) {
            /* steps 5-6 (not applicable) */
            /* steps 4, 7 */
            getBase().setMutableBinding(getReferencedName(), w, isStrictReference());
        }

        @Override
        public boolean delete(ExecutionContext cx) {
            /* steps 1-3 (generated code) */
            /* steps 4-5 (not applicable) */
            /* step 6 */
            return getBase().deleteBinding(getReferencedName());
        }

        @Override
        public EnvironmentRecord getThisValue() {
            throw new AssertionError();
        }

        @Override
        public void initializeReferencedBinding(Object w) {
            /* steps 1-4 (not applicable) */
            /* steps 5-7 */
            getBase().initializeBinding(getReferencedName(), w);
        }
    }

    /**
     * Reference specialization for unresolvable references.
     */
    public static final class UnresolvableReference extends Reference<Void, String> {
        private final String referencedName;
        private final boolean strictReference;

        public UnresolvableReference(String referencedName, boolean strictReference) {
            this.referencedName = referencedName;
            this.strictReference = strictReference;
        }

        @Override
        public Void getBase() {
            return null;
        }

        @Override
        public String getReferencedName() {
            return referencedName;
        }

        @Override
        public boolean isStrictReference() {
            return strictReference;
        }

        @Override
        public boolean hasPrimitiveBase() {
            return false;
        }

        @Override
        public boolean isPropertyReference() {
            return false;
        }

        @Override
        public boolean isUnresolvableReference() {
            return true;
        }

        @Override
        public boolean isSuperReference() {
            return false;
        }

        @Override
        public Object getValue(ExecutionContext cx) {
            /* step 4 */
            throw newReferenceError(cx, Messages.Key.UnresolvableReference, getReferencedName());
        }

        @Override
        public void putValue(Object w, ExecutionContext cx) {
            /* step 5 */
            if (isStrictReference()) {
                throw newReferenceError(cx, Messages.Key.UnresolvableReference, getReferencedName());
            }
            Set(cx, cx.getGlobalObject(), getReferencedName(), w, false);
        }

        @Override
        public boolean delete(ExecutionContext cx) {
            /* steps 1-3 (generated code) */
            /* steps 5-6 (not applicable) */
            /* step 4 */
            assert !isStrictReference();
            return true;
        }

        @Override
        public EnvironmentRecord getThisValue() {
            throw new AssertionError();
        }

        @Override
        public void initializeReferencedBinding(Object w) {
            throw new AssertionError();
        }
    }

    /**
     * Reference specialization for property references.
     *
     * @param <NAME>
     *            the reference name type
     */
    protected static abstract class PropertyReference<NAME> extends Reference<Object, NAME> {
        protected final Object base;
        protected final Type type;
        protected final boolean strictReference;

        /**
         * Constructs a new property reference.
         * 
         * @param base
         *            the base object
         * @param strictReference
         *            the strict mode flag
         */
        protected PropertyReference(Object base, boolean strictReference) {
            this.base = base;
            this.type = Type.of(base);
            this.strictReference = strictReference;
            assert !(type == Type.Undefined || type == Type.Null);
        }

        @Override
        public final Object getBase() {
            return base;
        }

        @Override
        public final boolean isStrictReference() {
            return strictReference;
        }

        @Override
        public final boolean hasPrimitiveBase() {
            return type != Type.Object;
        }

        @Override
        public final boolean isPropertyReference() {
            return true;
        }

        @Override
        public final boolean isUnresolvableReference() {
            return false;
        }

        @Override
        public final boolean isSuperReference() {
            return false;
        }

        @Override
        public final Object getThisValue() {
            /* steps 1-2 (not applicable) */
            /* step 3 */
            return getBase();
        }

        @Override
        public final void initializeReferencedBinding(Object w) {
            throw new AssertionError();
        }

        protected final OrdinaryObject getPrimitiveBaseProto(ExecutionContext cx) {
            switch (type) {
            case Boolean:
                return cx.getIntrinsic(Intrinsics.BooleanPrototype);
            case BigInt:
                return cx.getIntrinsic(Intrinsics.BigIntPrototype);
            case Number:
                return cx.getIntrinsic(Intrinsics.NumberPrototype);
            case String:
                return cx.getIntrinsic(Intrinsics.StringPrototype);
            case Symbol:
                return cx.getIntrinsic(Intrinsics.SymbolPrototype);
            case SIMD:
                return cx.getIntrinsic(getSIMDBaseProto(base));
            default:
                throw new AssertionError();
            }
        }

        protected static final OrdinaryObject getPrimitiveBaseProto(ExecutionContext cx, Object base) {
            switch (Type.of(base)) {
            case Boolean:
                return cx.getIntrinsic(Intrinsics.BooleanPrototype);
            case BigInt:
                return cx.getIntrinsic(Intrinsics.BigIntPrototype);
            case Number:
                return cx.getIntrinsic(Intrinsics.NumberPrototype);
            case String:
                return cx.getIntrinsic(Intrinsics.StringPrototype);
            case Symbol:
                return cx.getIntrinsic(Intrinsics.SymbolPrototype);
            case SIMD:
                return cx.getIntrinsic(getSIMDBaseProto(base));
            default:
                throw new AssertionError();
            }
        }

        private static Intrinsics getSIMDBaseProto(Object base) {
            switch (Type.simdValue(base).getType()) {
            case Float64x2:
                return Intrinsics.SIMD_Float64x2Prototype;
            case Float32x4:
                return Intrinsics.SIMD_Float32x4Prototype;
            case Int32x4:
                return Intrinsics.SIMD_Int32x4Prototype;
            case Int16x8:
                return Intrinsics.SIMD_Int16x8Prototype;
            case Int8x16:
                return Intrinsics.SIMD_Int8x16Prototype;
            case Uint32x4:
                return Intrinsics.SIMD_Uint32x4Prototype;
            case Uint16x8:
                return Intrinsics.SIMD_Uint16x8Prototype;
            case Uint8x16:
                return Intrinsics.SIMD_Uint8x16Prototype;
            case Bool64x2:
                return Intrinsics.SIMD_Bool64x2Prototype;
            case Bool32x4:
                return Intrinsics.SIMD_Bool32x4Prototype;
            case Bool16x8:
                return Intrinsics.SIMD_Bool16x8Prototype;
            case Bool8x16:
                return Intrinsics.SIMD_Bool8x16Prototype;
            default:
                throw new AssertionError();
            }
        }
    }

    /**
     * Reference specialization for indexed property references.
     */
    public static final class PropertyIndexReference extends PropertyReference<String> {
        private final long referencedName;

        /**
         * Constructs a new property reference.
         * 
         * @param base
         *            the base object
         * @param referencedName
         *            the referenced name
         * @param strictReference
         *            the strict mode flag
         */
        public PropertyIndexReference(Object base, long referencedName, boolean strictReference) {
            super(base, strictReference);
            this.referencedName = referencedName;
        }

        @Override
        public String getReferencedName() {
            return ToString(referencedName);
        }

        @Override
        public Object getValue(ExecutionContext cx) {
            /* steps 4, 6 (not applicable) */
            /* steps 3, 5.a */
            if (hasPrimitiveBase()) {
                // base = ToObject(realm, base);
                return getValuePrimitive(cx);
            }
            /* steps 3, 5.b */
            return ((ScriptObject) getBase()).get(cx, referencedName, getThisValue());
        }

        @Override
        public void putValue(Object w, ExecutionContext cx) {
            ScriptObject base = hasPrimitiveBase() ? ToObject(cx, getBase()) : (ScriptObject) getBase();
            boolean succeeded = base.set(cx, referencedName, w, getThisValue());
            if (!succeeded && isStrictReference()) {
                throw newTypeError(cx, Messages.Key.PropertyNotModifiable, getReferencedName());
            }
        }

        @Override
        public boolean delete(ExecutionContext cx) {
            /* steps 1-3 (generated code) */
            /* steps 4, 6 (not applicable) */
            /* step 5 */
            ScriptObject base = hasPrimitiveBase() ? ToObject(cx, getBase()) : (ScriptObject) getBase();
            boolean deleteStatus = base.delete(cx, referencedName);
            if (!deleteStatus && isStrictReference()) {
                throw newTypeError(cx, Messages.Key.PropertyNotDeletable, getReferencedName());
            }
            return deleteStatus;
        }

        private Object getValuePrimitive(ExecutionContext cx) {
            long refName = referencedName;
            if (type == Type.String && 0 <= refName && refName < 0x7FFF_FFFFL) {
                int index = (int) refName;
                CharSequence str = Type.stringValue(getBase());
                if (index < str.length()) {
                    return String.valueOf(str.charAt(index));
                }
            }
            return getPrimitiveBaseProto(cx).get(cx, refName, getBase());
        }

        /**
         * Returns the property value:<br>
         * {@code base[referencedName]}.
         * 
         * @param cx
         *            the execution context
         * @param base
         *            the base value
         * @param referencedName
         *            the property name
         * @return the property value
         */
        public static Object GetValue(ExecutionContext cx, Object base, long referencedName) {
            if (base instanceof ScriptObject) {
                return ((ScriptObject) base).get(cx, referencedName, base);
            }
            if (Type.isString(base)) {
                CharSequence str = Type.stringValue(base);
                if (referencedName < str.length()) {
                    return String.valueOf(str.charAt((int) referencedName));
                }
            }
            return getPrimitiveBaseProto(cx, base).get(cx, referencedName, base);
        }

        /**
         * Sets the property to:<br>
         * {@code base[referencedName] = value}.
         * 
         * @param cx
         *            the execution context
         * @param base
         *            the base value
         * @param referencedName
         *            the property name
         * @param value
         *            the new property value
         * @param strict
         *            the strict-mode flag
         */
        public static void PutValue(ExecutionContext cx, Object base, long referencedName, Object value,
                boolean strict) {
            boolean succeeded = ToObject(cx, base).set(cx, referencedName, value, base);
            if (!succeeded && strict) {
                throw newTypeError(cx, Messages.Key.PropertyNotModifiable, ToString(referencedName));
            }
        }

        /**
         * Deletes the property:<br>
         * {@code delete base[referencedName]}.
         * 
         * @param cx
         *            the execution context
         * @param base
         *            the base value
         * @param referencedName
         *            the property name
         * @param strict
         *            the strict-mode flag
         * @return {@code true} on success
         */
        public static boolean Delete(ExecutionContext cx, Object base, long referencedName, boolean strict) {
            boolean deleteStatus = ToObject(cx, base).delete(cx, referencedName);
            if (!deleteStatus && strict) {
                throw newTypeError(cx, Messages.Key.PropertyNotDeletable, Long.toString(referencedName));
            }
            return deleteStatus;
        }
    }

    /**
     * Reference specialization for string-valued property references.
     */
    public static final class PropertyNameReference extends PropertyReference<String> {
        private final String referencedName;

        /**
         * Constructs a new property reference.
         * 
         * @param base
         *            the base object
         * @param referencedName
         *            the referenced name
         * @param strictReference
         *            the strict mode flag
         */
        public PropertyNameReference(Object base, String referencedName, boolean strictReference) {
            super(base, strictReference);
            this.referencedName = referencedName;
        }

        @Override
        public String getReferencedName() {
            return referencedName;
        }

        @Override
        public Object getValue(ExecutionContext cx) {
            /* steps 4, 6 (not applicable) */
            /* steps 3, 5.a */
            if (hasPrimitiveBase()) {
                // base = ToObject(realm, base);
                return getValuePrimitive(cx);
            }
            /* steps 3, 5.b */
            return ((ScriptObject) getBase()).get(cx, referencedName, getThisValue());
        }

        @Override
        public void putValue(Object w, ExecutionContext cx) {
            ScriptObject base = hasPrimitiveBase() ? ToObject(cx, getBase()) : (ScriptObject) getBase();
            boolean succeeded = base.set(cx, referencedName, w, getThisValue());
            if (!succeeded && isStrictReference()) {
                throw newTypeError(cx, Messages.Key.PropertyNotModifiable, getReferencedName());
            }
        }

        @Override
        public boolean delete(ExecutionContext cx) {
            /* steps 1-3 (generated code) */
            /* steps 4, 6 (not applicable) */
            /* step 5 */
            ScriptObject base = hasPrimitiveBase() ? ToObject(cx, getBase()) : (ScriptObject) getBase();
            boolean deleteStatus = base.delete(cx, referencedName);
            if (!deleteStatus && isStrictReference()) {
                throw newTypeError(cx, Messages.Key.PropertyNotDeletable, getReferencedName());
            }
            return deleteStatus;
        }

        private Object getValuePrimitive(ExecutionContext cx) {
            if (type == Type.String && "length".equals(referencedName)) {
                return Type.stringValue(getBase()).length();
            }
            return getPrimitiveBaseProto(cx).get(cx, referencedName, getBase());
        }

        /**
         * Returns the property value:<br>
         * {@code base[referencedName]}.
         * 
         * @param cx
         *            the execution context
         * @param base
         *            the base value
         * @param referencedName
         *            the property name
         * @return the property value
         */
        public static Object GetValue(ExecutionContext cx, Object base, String referencedName) {
            if (base instanceof ScriptObject) {
                return ((ScriptObject) base).get(cx, referencedName, base);
            }
            if (Type.isString(base) && "length".equals(referencedName)) {
                return Type.stringValue(base).length();
            }
            return getPrimitiveBaseProto(cx, base).get(cx, referencedName, base);
        }

        /**
         * Sets the property to:<br>
         * {@code base[referencedName] = value}.
         * 
         * @param cx
         *            the execution context
         * @param base
         *            the base value
         * @param referencedName
         *            the property name
         * @param value
         *            the new property value
         * @param strict
         *            the strict-mode flag
         */
        public static void PutValue(ExecutionContext cx, Object base, String referencedName, Object value,
                boolean strict) {
            boolean succeeded = ToObject(cx, base).set(cx, referencedName, value, base);
            if (!succeeded && strict) {
                throw newTypeError(cx, Messages.Key.PropertyNotModifiable, referencedName);
            }
        }

        /**
         * Deletes the property:<br>
         * {@code delete base[referencedName]}.
         * 
         * @param cx
         *            the execution context
         * @param base
         *            the base value
         * @param referencedName
         *            the property name
         * @param strict
         *            the strict-mode flag
         * @return {@code true} on success
         */
        public static boolean Delete(ExecutionContext cx, Object base, String referencedName, boolean strict) {
            boolean deleteStatus = ToObject(cx, base).delete(cx, referencedName);
            if (!deleteStatus && strict) {
                throw newTypeError(cx, Messages.Key.PropertyNotDeletable, referencedName);
            }
            return deleteStatus;
        }
    }

    /**
     * Reference specialization for symbol-valued property references.
     */
    public static final class PropertySymbolReference extends PropertyReference<Symbol> {
        private final Symbol referencedName;

        /**
         * Constructs a new property reference.
         * 
         * @param base
         *            the base object
         * @param referencedName
         *            the referenced name
         * @param strictReference
         *            the strict mode flag
         */
        public PropertySymbolReference(Object base, Symbol referencedName, boolean strictReference) {
            super(base, strictReference);
            this.referencedName = referencedName;
        }

        @Override
        public Symbol getReferencedName() {
            return referencedName;
        }

        @Override
        public Object getValue(ExecutionContext cx) {
            /* steps 4, 6 (not applicable) */
            /* steps 3, 5.a */
            if (hasPrimitiveBase()) {
                // base = ToObject(realm, base);
                return getPrimitiveBaseProto(cx).get(cx, referencedName, getThisValue());
            }
            /* steps 3, 5.b */
            return ((ScriptObject) getBase()).get(cx, referencedName, getThisValue());
        }

        @Override
        public void putValue(Object w, ExecutionContext cx) {
            ScriptObject base = hasPrimitiveBase() ? ToObject(cx, getBase()) : (ScriptObject) getBase();
            boolean succeeded = base.set(cx, referencedName, w, getThisValue());
            if (!succeeded && isStrictReference()) {
                throw newTypeError(cx, Messages.Key.PropertyNotModifiable, getReferencedName().toString());
            }
        }

        @Override
        public boolean delete(ExecutionContext cx) {
            /* steps 1-3 (generated code) */
            /* steps 4, 6 (not applicable) */
            /* step 5 */
            ScriptObject base = hasPrimitiveBase() ? ToObject(cx, getBase()) : (ScriptObject) getBase();
            boolean deleteStatus = base.delete(cx, referencedName);
            if (!deleteStatus && isStrictReference()) {
                throw newTypeError(cx, Messages.Key.PropertyNotDeletable, getReferencedName().toString());
            }
            return deleteStatus;
        }

        /**
         * Returns the property value:<br>
         * {@code base[referencedName]}.
         * 
         * @param cx
         *            the execution context
         * @param base
         *            the base value
         * @param referencedName
         *            the property name
         * @return the property value
         */
        public static Object GetValue(ExecutionContext cx, Object base, Symbol referencedName) {
            if (base instanceof ScriptObject) {
                return ((ScriptObject) base).get(cx, referencedName, base);
            }
            return getPrimitiveBaseProto(cx, base).get(cx, referencedName, base);
        }

        /**
         * Sets the property to:<br>
         * {@code base[referencedName] = value}.
         * 
         * @param cx
         *            the execution context
         * @param base
         *            the base value
         * @param referencedName
         *            the property name
         * @param value
         *            the new property value
         * @param strict
         *            the strict-mode flag
         */
        public static void PutValue(ExecutionContext cx, Object base, Symbol referencedName, Object value,
                boolean strict) {
            boolean succeeded = ToObject(cx, base).set(cx, referencedName, value, base);
            if (!succeeded && strict) {
                throw newTypeError(cx, Messages.Key.PropertyNotModifiable, referencedName.toString());
            }
        }

        /**
         * Deletes the property:<br>
         * {@code delete base[referencedName]}.
         * 
         * @param cx
         *            the execution context
         * @param base
         *            the base value
         * @param referencedName
         *            the property name
         * @param strict
         *            the strict-mode flag
         * @return {@code true} on success
         */
        public static boolean Delete(ExecutionContext cx, Object base, Symbol referencedName, boolean strict) {
            boolean deleteStatus = ToObject(cx, base).delete(cx, referencedName);
            if (!deleteStatus && strict) {
                throw newTypeError(cx, Messages.Key.PropertyNotDeletable, referencedName.toString());
            }
            return deleteStatus;
        }
    }

    /**
     * Reference specialization for <code>super</code> references.
     *
     * @param <NAME>
     *            the reference name type
     */
    protected static abstract class SuperReference<NAME> extends Reference<ScriptObject, NAME> {
        private final ScriptObject base;
        private final boolean strictReference;
        private final Object thisValue;

        /**
         * Constructs a new <code>super</code> reference.
         * 
         * @param base
         *            the base object
         * @param strictReference
         *            the strict mode flag
         * @param thisValue
         *            the this-binding
         */
        protected SuperReference(ScriptObject base, boolean strictReference, Object thisValue) {
            this.base = base;
            this.strictReference = strictReference;
            this.thisValue = thisValue;
        }

        @Override
        public final ScriptObject getBase() {
            return base;
        }

        @Override
        public final boolean isStrictReference() {
            return strictReference;
        }

        @Override
        public final boolean hasPrimitiveBase() {
            return false;
        }

        @Override
        public final boolean isPropertyReference() {
            return true;
        }

        @Override
        public final boolean isUnresolvableReference() {
            return false;
        }

        @Override
        public final boolean isSuperReference() {
            return true;
        }

        @Override
        public final Object getThisValue() {
            /* steps 1, 3 (not applicable) */
            /* step 2 */
            return thisValue;
        }

        @Override
        public final boolean delete(ExecutionContext cx) {
            throw newReferenceError(cx, Messages.Key.SuperDelete);
        }

        @Override
        public final void initializeReferencedBinding(Object w) {
            throw new AssertionError();
        }
    }

    /**
     * Reference specialization for string-valued <code>super</code> references.
     */
    public static final class SuperNameReference extends SuperReference<String> {
        private final String referencedName;

        /**
         * Constructs a new <code>super</code> reference.
         * 
         * @param base
         *            the base object
         * @param referencedName
         *            the referenced name
         * @param strictReference
         *            the strict mode flag
         * @param thisValue
         *            the this-binding
         */
        public SuperNameReference(ScriptObject base, String referencedName, boolean strictReference, Object thisValue) {
            super(base, strictReference, thisValue);
            this.referencedName = referencedName;
        }

        @Override
        public String getReferencedName() {
            return referencedName;
        }

        @Override
        public Object getValue(ExecutionContext cx) {
            return getBase().get(cx, getReferencedName(), getThisValue());
        }

        @Override
        public void putValue(Object w, ExecutionContext cx) {
            boolean succeeded = getBase().set(cx, getReferencedName(), w, getThisValue());
            if (!succeeded && isStrictReference()) {
                throw newTypeError(cx, Messages.Key.PropertyNotModifiable, getReferencedName());
            }
        }
    }

    /**
     * Reference specialization for symbol-valued <code>super</code> references.
     */
    public static final class SuperSymbolReference extends SuperReference<Symbol> {
        private final Symbol referencedName;

        /**
         * Constructs a new <code>super</code> reference.
         * 
         * @param base
         *            the base object
         * @param referencedName
         *            the referenced name
         * @param strictReference
         *            the strict mode flag
         * @param thisValue
         *            the this-binding
         */
        public SuperSymbolReference(ScriptObject base, Symbol referencedName, boolean strictReference,
                Object thisValue) {
            super(base, strictReference, thisValue);
            this.referencedName = referencedName;
        }

        @Override
        public Symbol getReferencedName() {
            return referencedName;
        }

        @Override
        public Object getValue(ExecutionContext cx) {
            return getBase().get(cx, getReferencedName(), getThisValue());
        }

        @Override
        public void putValue(Object w, ExecutionContext cx) {
            boolean succeeded = getBase().set(cx, getReferencedName(), w, getThisValue());
            if (!succeeded && isStrictReference()) {
                throw newTypeError(cx, Messages.Key.PropertyNotModifiable, getReferencedName().toString());
            }
        }
    }
}
