/*
 * 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.tinkerpop.gremlin.process.traversal;

import org.apache.tinkerpop.gremlin.process.traversal.step.map.LoopsStep;
import org.apache.tinkerpop.gremlin.structure.util.Attachable;

import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;

/**
 * A {@code Traverser} represents the current state of an object flowing through a {@link Traversal}.
 * A traverser maintains a reference to the current object, a traverser-local "sack", a traversal-global sideEffect,
 * a bulk count, and a path history.
 * <p/>
 * Different types of traversers can exist depending on the semantics of the traversal and the desire for
 * space/time optimizations of the developer.
 *
 * @author Marko A. Rodriguez (http://markorodriguez.com)
 */
public interface Traverser<T> extends Serializable, Comparable<Traverser<T>>, Cloneable {

    /**
     * Get the object that the traverser is current at.
     *
     * @return The current object of the traverser
     */
    public T get();

    /**
     * Get the sack local sack object of this traverser.
     *
     * @param <S> the type of the sack object
     * @return the sack object
     */
    public <S> S sack();

    /**
     * Set the traversers sack object to the provided value ("sack the value").
     *
     * @param object the new value of the traverser's sack
     * @param <S>    the type of the object
     */
    public <S> void sack(final S object);

    /**
     * Get the current path of the traverser.
     *
     * @return The path of the traverser
     */
    public Path path();

    /**
     * Get the object associated with the specified step-label in the traverser's path history.
     *
     * @param stepLabel the step-label in the path to access
     * @param <A>       the type of the object
     * @return the object associated with that path label (if more than one object occurs at that step, a list is returned)
     */
    public default <A> A path(final String stepLabel) {
        return this.path().get(stepLabel);
    }

    public default <A> A path(final Pop pop, final String stepLabel) {
        return this.path().get(pop, stepLabel);
    }

    /**
     * Return the number of times the traverser has gone through a looping section of a traversal.
     *
     * @return The number of times the traverser has gone through a loop
     */
    public int loops();

    /**
     * Return the number of times the traverser has gone through the named looping section of a traversal.
     *
     * @param loopName the name applied to the loop or null for the containing loop
     * @return The number of times the traverser has gone through a loop
     * @throws IllegalArgumentException if the loopName is not defined
     */
    public int loops(final String loopName);

    /**
     * A traverser may represent a grouping of traversers to allow for more efficient data propagation.
     *
     * @return the number of traversers represented in this traverser.
     */
    public long bulk();

    /**
     * Get a particular value from the side-effects of the traverser (thus, traversal).
     *
     * @param sideEffectKey the key of the value to get from the sideEffects
     * @param <A>           the type of the returned object
     * @return the object in the sideEffects of the respective key
     */
    public default <A> A sideEffects(final String sideEffectKey) throws IllegalArgumentException {
        return this.asAdmin().getSideEffects().<A>get(sideEffectKey);
    }

    /**
     * Add a particular value to the respective side-effect of the traverser (thus, traversal).
     *
     * @param sideEffectKey   the key of the value to set int the sideEffects
     * @param sideEffectValue the value to set for the sideEffect key
     */
    public default void sideEffects(final String sideEffectKey, final Object sideEffectValue) throws IllegalArgumentException {
        this.asAdmin().getSideEffects().add(sideEffectKey, sideEffectValue);
    }

    /**
     * If the underlying object of the traverser is comparable, compare it with the other traverser.
     *
     * @param other the other traverser that presumably has a comparable internal object
     * @return the comparison of the two objects of the traversers
     * @throws ClassCastException if the object of the traverser is not comparable
     */
    @Override
    public default int compareTo(final Traverser<T> other) throws ClassCastException {
        final Object thisObj = this.get();
        final Object otherObj = other.get();
        if (thisObj == otherObj) return 0;
        if (null == thisObj) return -1;
        if (null == otherObj) return 1;
        return ((Comparable) thisObj).compareTo(otherObj);
    }

    /**
     * Typecast the traverser to a "system traverser" so {@link Traverser.Admin} methods can be accessed.
     * This is used as a helper method to avoid the awkwardness of <code>((Traverser.Administrative)traverser)</code>.
     * The default implementation simply returns "this" type casted to {@link Traverser.Admin}.
     *
     * @return The type-casted traverser
     */
    public default Admin<T> asAdmin() {
        return (Admin<T>) this;
    }

    /**
     * Traverser cloning is important when splitting a traverser at a bifurcation point in a traversal.
     */
    @SuppressWarnings("CloneDoesntDeclareCloneNotSupportedException")
    public Traverser<T> clone();

    /**
     * The methods in System.Traverser are useful to underlying Step and Traversal implementations.
     * They should not be accessed by the user during lambda-based manipulations.
     */
    public interface Admin<T> extends Traverser<T>, Attachable<T> {

        public static final String HALT = "halt";

        /**
         * When two traversers are have equality with each other, then they can be merged.
         * This method is used to merge the traversers into a single traverser.
         * This is used for optimization where instead of enumerating all traversers, they can be counted.
         *
         * @param other the other traverser to merge into this traverser. Once merged, the other can be garbage collected.
         */
        public void merge(final Admin<?> other);

        /**
         * Generate a child traverser of the current traverser for current as step and new object location.
         * The child has the path history, future, and loop information of the parent.
         * The child extends that path history with the current as and provided R-object.
         *
         * @param r    The current object of the child
         * @param step The step yielding the split
         * @param <R>  The current object type of the child
         * @return The split traverser
         */
        public <R> Admin<R> split(final R r, final Step<T, R> step);

        /**
         * Generate a sibling traverser of the current traverser with a full copy of all state within the sibling.
         *
         * @return The split traverser
         */
        public Admin<T> split();

        public void addLabels(final Set<String> labels);

        /**
         * Drop all path information not associated with specified labels.
         * This is an optimization method that allows a traverser to save memory and increase the likelihood of bulking.
         *
         * @param labels the labels to keep path information for.
         */
        public void keepLabels(final Set<String> labels);

        /**
         * Drop all path information associated with specified labels.
         * This is an optimization method that allows a traverser to save memory and increase the likelihood of bulking.
         *
         * @param labels the labels to drop path information for.
         */
        public void dropLabels(final Set<String> labels);

        /**
         * Drop the path of the traverser.
         * This is an optimization method that allows a traverser to save memory and increase the likelihood of bulking.
         */
        public void dropPath();

        /**
         * Set the current object location of the traverser.
         *
         * @param t The current object of the traverser
         */
        public void set(final T t);

        /**
         * Initialise a loop by setting up the looping construct.
         * The step label is important to create a stack of loop counters when within a nested context.
         * If the provided label is not the same as the current label on the stack, add a new loop counter.
         * The loopName can be used to refer to the loops counter via the {@link LoopsStep}
         *
         * @param stepLabel the label of the step that is being set-up.
         * @param loopName the user defined name for referencing the loop counter or null if not set
         */
        public void initialiseLoops(final String stepLabel, final String loopName);

        /**
         * Increment the number of times the traverser has gone through a looping section of traversal.
         */
        public void incrLoops();

        /**
         * Set the number of times the traverser has gone through a loop back to 0.
         * When a traverser exits a looping construct, this method should be called.
         * In a nested loop context, the highest stack loop counter should be removed.
         */
        public void resetLoops();

        /**
         * Get the step id of where the traverser is located.
         * This is typically used in multi-machine systems that require the movement of
         * traversers between different traversal instances.
         *
         * @return The future step for the traverser
         */
        public String getStepId();

        /**
         * Set the step id of where the traverser is located.
         * If the future is {@link Traverser.Admin#HALT}, then {@link Traverser.Admin#isHalted()} is true.
         *
         * @param stepId The future step of the traverser
         */
        public void setStepId(final String stepId);

        /**
         * If the traverser has "no future" then it is done with its lifecycle.
         * This does not mean that the traverser is "dead," only that it has successfully passed through a
         * {@link Traversal}.
         *
         * @return Whether the traverser is done executing or not
         */
        public default boolean isHalted() {
            return getStepId().equals(HALT);
        }

        /**
         * Set the number of traversers represented by this traverser.
         *
         * @param count the number of traversers
         */
        public void setBulk(final long count);

        /**
         * Prepare the traverser for migration across a JVM boundary.
         *
         * @return The deflated traverser
         */
        public Admin<T> detach();

        /**
         * Regenerate the detached traverser given its location at a particular vertex.
         *
         * @param method The method by which to attach a {@code Traverser} to an vertex.
         * @return The inflated traverser
         */
        @Override
        public T attach(final Function<Attachable<T>, T> method);

        /**
         * Set the sideEffects of the {@link Traversal}. Given that traversers can move between machines,
         * it may be important to re-set this when the traverser crosses machine boundaries.
         *
         * @param sideEffects the sideEffects of the traversal.
         */
        public void setSideEffects(final TraversalSideEffects sideEffects);

        /**
         * Get the sideEffects associated with the traversal of the traverser.
         *
         * @return the traversal sideEffects of the traverser
         */
        public TraversalSideEffects getSideEffects();

        /**
         * Get the tags associated with the traverser.
         * Tags are used to categorize historic behavior of a traverser.
         * The returned set is mutable.
         *
         * @return the set of tags associated with the traverser.
         */
        public Set<String> getTags();

        /**
         * If this traverser supports loops then return the loop names if any.
         */
        public default Set<String> getLoopNames() {
            return Collections.emptySet();
        }
    }
}
