/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.cassandra.db.transform;

import java.util.Iterator;
import java.util.NoSuchElementException;

import net.nicoulaj.compilecommand.annotations.DontInline;
import org.apache.cassandra.utils.CloseableIterator;

import static org.apache.cassandra.utils.Throwables.maybeFail;
import static org.apache.cassandra.utils.Throwables.merge;

abstract class BaseIterator<V, I extends CloseableIterator<? extends V>, O extends V> extends Stack implements AutoCloseable, Iterator<O>
{
    I input;
    V next;

    // We require two stop signals for correctness, since the `stop` reference of the base iterator can "leak"
    // into the transformations stack. Using a single `stop` signal may result into the inconsistent state,
    // since stopping transformation would stop only the child iterator.

    // Signals that the base iterator has been signalled to stop. Applies at the end of the current next().
    Stop stop;
    // Signals that the current child iterator has been signalled to stop.
    Stop stopChild;

    // Multiple call to close can have some side effects on the Transformations. By consequence if the iterator is
    // already closed we want to ignore extra calls to close.
    private boolean closed;

    static class Stop
    {
        // TODO: consider moving "next" into here, so that a stop() when signalled outside of a function call (e.g. in attach)
        // can take effect immediately; this doesn't seem to be necessary at the moment, but it might cause least surprise in future
        boolean isSignalled;
    }

    // responsibility for initialising next lies with the subclass
    BaseIterator(BaseIterator<? extends V, ? extends I, ?> copyFrom)
    {
        super(copyFrom);
        this.input = copyFrom.input;
        this.next = copyFrom.next;
        this.stop = copyFrom.stop;
        this.stopChild = copyFrom.stopChild;
    }

    BaseIterator(I input)
    {
        this.input = input;
        this.stop = new Stop();
        this.stopChild = this.stop;
    }

    /**
     * run the corresponding runOnClose method for the first length transformations.
     *
     * used in hasMoreContents to close the methods preceding the MoreContents
     */
    protected abstract Throwable runOnClose(int length);

    /**
     * apply the relevant method from the transformation to the value.
     *
     * used in hasMoreContents to apply the functions that follow the MoreContents
     */
    protected abstract V applyOne(V value, Transformation transformation);

    public final void close()
    {
        // If close has already been called we want to ignore other calls
        if (closed)
            return;

        closed = true;
        Throwable fail = runOnClose(length);
        if (next instanceof AutoCloseable)
        {
            try 
            {
                ((AutoCloseable) next).close();
            }
            catch (Throwable t)
            {
                fail = merge(fail, t);
            }
        }
        try
        {
            input.close();
        }
        catch (Throwable t)
        {
            fail = merge(fail, t);
        }
        maybeFail(fail);
    }

    public final O next()
    {
        if (next == null && !hasNext())
            throw new NoSuchElementException();

        O next = (O) this.next;
        this.next = null;
        return next;
    }

    // may set next != null if the next contents are a transforming iterator that already has data to return,
    // in which case we immediately have more contents to yield
    protected final boolean hasMoreContents()
    {
        return moreContents.length > 0 && tryGetMoreContents();
    }

    @DontInline
    private boolean tryGetMoreContents()
    {
        for (int i = 0 ; i < moreContents.length ; i++)
        {
            MoreContentsHolder holder = moreContents[i];
            MoreContents provider = holder.moreContents;
            I newContents = (I) provider.moreContents();
            if (newContents == null)
                continue;

            input.close();
            input = newContents;
            Stack prefix = EMPTY;
            if (newContents instanceof BaseIterator)
            {
                // we're refilling with transformed contents, so swap in its internals directly
                // TODO: ensure that top-level data is consistent. i.e. staticRow, partitionlevelDeletion etc are same?
                BaseIterator abstr = (BaseIterator) newContents;
                prefix = abstr;
                input = (I) abstr.input;
                stopChild = abstr.stop;
                next = apply((V) abstr.next, holder.length); // must apply all remaining functions to the next, if any
            }

            // since we're truncating our transformation stack to only those occurring after the extend transformation
            // we have to run any prior runOnClose methods
            maybeFail(runOnClose(holder.length));
            refill(prefix, holder, i);

            if (next != null || input.hasNext())
                return true;

            i = -1;
        }
        return false;
    }

    // apply the functions [from..length)
    private V apply(V next, int from)
    {
        while (next != null & from < length)
            next = applyOne(next, stack[from++]);
        return next;
    }
}

