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

import static com.github.anba.es6draft.runtime.AbstractOperations.CreateArrayFromList;
import static com.github.anba.es6draft.runtime.AbstractOperations.CreateIterResultObject;
import static com.github.anba.es6draft.runtime.AbstractOperations.Get;
import static com.github.anba.es6draft.runtime.AbstractOperations.ToLength;
import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError;
import static com.github.anba.es6draft.runtime.internal.Properties.createProperties;
import static com.github.anba.es6draft.runtime.objects.binary.ArrayBufferConstructor.IsDetachedBuffer;
import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED;

import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.Realm;
import com.github.anba.es6draft.runtime.internal.Initializable;
import com.github.anba.es6draft.runtime.internal.Messages;
import com.github.anba.es6draft.runtime.internal.Properties.Attributes;
import com.github.anba.es6draft.runtime.internal.Properties.Function;
import com.github.anba.es6draft.runtime.internal.Properties.Prototype;
import com.github.anba.es6draft.runtime.internal.Properties.Value;
import com.github.anba.es6draft.runtime.objects.ArrayIteratorObject.ArrayIterationKind;
import com.github.anba.es6draft.runtime.objects.binary.TypedArrayObject;
import com.github.anba.es6draft.runtime.types.BuiltinSymbol;
import com.github.anba.es6draft.runtime.types.Intrinsics;
import com.github.anba.es6draft.runtime.types.ScriptObject;
import com.github.anba.es6draft.runtime.types.Type;
import com.github.anba.es6draft.runtime.types.builtins.NativeFunction;
import com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject;

/**
 * <h1>22 Indexed Collections</h1><br>
 * <h2>22.1 Array Objects</h2>
 * <ul>
 * <li>22.1.5 Array Iterator Objects
 * </ul>
 */
public final class ArrayIteratorPrototype extends OrdinaryObject implements Initializable {
    /**
     * Constructs a new Array Iterator prototype object.
     * 
     * @param realm
     *            the realm object
     */
    public ArrayIteratorPrototype(Realm realm) {
        super(realm);
    }

    @Override
    public void initialize(Realm realm) {
        createProperties(realm, this, Properties.class);
    }

    /**
     * 22.1.5.1 CreateArrayIterator Abstract Operation
     * 
     * @param cx
     *            the execution context
     * @param array
     *            the array-like object
     * @param kind
     *            the array iteration kind
     * @return the new array iterator
     */
    public static ArrayIteratorObject CreateArrayIterator(ExecutionContext cx, ScriptObject array,
            ArrayIterationKind kind) {
        /* step 1 (not applicable) */
        /* steps 2-6 */
        return new ArrayIteratorObject(cx.getRealm(), array, kind, cx.getIntrinsic(Intrinsics.ArrayIteratorPrototype));
    }

    /**
     * 22.1.5.1 CreateArrayIterator Abstract Operation
     * 
     * @param cx
     *            the execution context
     * @param array
     *            the array-like object
     * @param index
     *            the start index
     * @param kind
     *            the array iteration kind
     * @return the new array iterator
     */
    public static ArrayIteratorObject CreateArrayIterator(ExecutionContext cx, ScriptObject array, long index,
            ArrayIterationKind kind) {
        /* step 1 (not applicable) */
        /* steps 2-6 */
        return new ArrayIteratorObject(cx.getRealm(), array, index, kind,
                cx.getIntrinsic(Intrinsics.ArrayIteratorPrototype));
    }

    /**
     * Marker class for {@code %ArrayIteratorPrototype%.next}.
     */
    private static final class ArrayIteratorPrototypeNext {
    }

    /**
     * Returns {@code true} if <var>next</var> is the built-in {@code %ArrayIteratorPrototype%.next} function for the
     * requested realm.
     * 
     * @param realm
     *            the function realm
     * @param next
     *            the next function
     * @return {@code true} if <var>next</var> is the built-in {@code %ArrayIteratorPrototype%.next} function
     */
    public static boolean isBuiltinNext(Realm realm, Object next) {
        return NativeFunction.isNative(realm, next, ArrayIteratorPrototypeNext.class);
    }

    /**
     * 22.1.5.2 The %ArrayIteratorPrototype% Object
     */
    public enum Properties {
        ;

        @Prototype
        public static final Intrinsics __proto__ = Intrinsics.IteratorPrototype;

        /**
         * 22.1.5.2.1 %ArrayIteratorPrototype%.next( )
         * 
         * @param cx
         *            the execution context
         * @param thisValue
         *            the function this-value
         * @return the next iterator result object
         */
        @Function(name = "next", arity = 0, nativeId = ArrayIteratorPrototypeNext.class)
        public static Object next(ExecutionContext cx, Object thisValue) {
            /* steps 1-3 */
            if (!(thisValue instanceof ArrayIteratorObject)) {
                throw newTypeError(cx, Messages.Key.IncompatibleThis, "%ArrayIteratorPrototype%.next",
                        Type.of(thisValue).toString());
            }
            ArrayIteratorObject iter = (ArrayIteratorObject) thisValue;
            /* step 4 */
            ScriptObject array = iter.getIteratedObject();
            /* step 5 */
            if (array == null) {
                return CreateIterResultObject(cx, UNDEFINED, true);
            }
            /* step 6 */
            long index = iter.getNextIndex();
            /* step 7 */
            ArrayIterationKind itemKind = iter.getIterationKind();
            /* steps 8-9 */
            long len;
            if (array instanceof TypedArrayObject) {
                TypedArrayObject typedArray = (TypedArrayObject) array;
                if (IsDetachedBuffer(typedArray.getBuffer())) {
                    throw newTypeError(cx, Messages.Key.BufferDetached);
                }
                len = typedArray.getArrayLength();
            } else {
                len = ToLength(cx, Get(cx, array, "length"));
            }
            /* step 10 */
            if (index >= len) {
                iter.setIteratedObject(null);
                return CreateIterResultObject(cx, UNDEFINED, true);
            }
            /* step 11 */
            iter.setNextIndex(index + 1);
            /* steps 12-16 */
            Object result;
            if (itemKind == ArrayIterationKind.Key) {
                /* step 12 */
                result = index;
            } else {
                /* step 13 */
                long elementKey = index;
                /* step 14 */
                Object elementValue = Get(cx, array, elementKey);
                if (itemKind == ArrayIterationKind.Value) {
                    /* step 15 */
                    result = elementValue;
                } else {
                    /* step 16 */
                    assert itemKind == ArrayIterationKind.KeyValue;
                    result = CreateArrayFromList(cx, index, elementValue);
                }
            }
            /* steps 12, 17 */
            return CreateIterResultObject(cx, result, false);
        }

        /**
         * 22.1.5.2.2 %ArrayIteratorPrototype% [ @@toStringTag ]
         */
        @Value(name = "[Symbol.toStringTag]", symbol = BuiltinSymbol.toStringTag,
                attributes = @Attributes(writable = false, enumerable = false, configurable = true))
        public static final String toStringTag = "Array Iterator";
    }
}
