/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.runtime.arrays;

import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.ScriptRuntime;
import jdk.nashorn.internal.runtime.linker.Bootstrap;

/**
 * Helper class for the various map/apply functions in {@link jdk.nashorn.internal.objects.NativeArray}.
 * @param <T> element type of results from application callback
 */
public abstract class IteratorAction<T> {
    /** Self object */
    protected final Object self;

    /** This for the callback invocation */
    protected Object thisArg;

    /** Callback function to be applied to elements */
    protected final Object callbackfn;

    /** Result of array iteration */
    protected T result;

    /** Current array index of iterator */
    protected long index;

    /** Iterator object */
    private final ArrayLikeIterator<Object> iter;

    /**
     * Constructor
     *
     * @param self          self reference to array object
     * @param callbackfn    callback function for each element
     * @param thisArg       the reference
     * @param initialResult result accumulator initialization
     */
    public IteratorAction(final Object self, final Object callbackfn, final Object thisArg, final T initialResult) {
        this(self, callbackfn, thisArg, initialResult, ArrayLikeIterator.arrayLikeIterator(self));
    }

    /**
     * Constructor
     *
     * @param self          self reference to array object
     * @param callbackfn    callback function for each element
     * @param thisArg       the reference
     * @param initialResult result accumulator initialization
     * @param iter          custom element iterator
     */
    public IteratorAction(final Object self, final Object callbackfn, final Object thisArg, final T initialResult, final ArrayLikeIterator<Object> iter) {
        this.self       = self;
        this.callbackfn = callbackfn;
        this.result     = initialResult;
        this.iter       = iter;
        this.thisArg    = thisArg;
    }

    /**
     * An action to be performed once at the start of the apply loop
     * @param iterator array element iterator
     */
    protected void applyLoopBegin(final ArrayLikeIterator<Object> iterator) {
        //empty
    }

    /**
     * Apply action main loop.
     * @return result of apply
     */
    public final T apply() {
        final boolean strict = Bootstrap.isStrictCallable(callbackfn);

        // for non-strict callback, need to translate undefined thisArg to be global object
        thisArg = (thisArg == ScriptRuntime.UNDEFINED && !strict)? Context.getGlobal() : thisArg;

        applyLoopBegin(iter);
        final boolean reverse = iter.isReverse();
        while (iter.hasNext()) {

            final Object val = iter.next();
            index = iter.nextIndex() + (reverse ? 1 : -1);

            try {
                if (!forEach(val, index)) {
                    return result;
                }
            } catch (final RuntimeException | Error e) {
                throw e;
            } catch (final Throwable t) {
                throw new RuntimeException(t);
            }
        }

        return result;
    }

    /**
     * For each callback
     *
     * @param val value
     * @param i   position of value
     *
     * @return true if callback invocation return true
     *
     * @throws Throwable if invocation throws an exception/error
     */
    protected abstract boolean forEach(final Object val, final double i) throws Throwable;

}
