import "copyrightAndOthers.stg"
import "primitiveLiteral.stg"

targetPath() ::= "org/eclipse/collections/api"

fileName(primitive) ::= "<primitive.name>Iterable"

class(primitive) ::= <<
<body(primitive.type, primitive.name)>
>>

body(type, name) ::= <<
<copyrightAndOthers()>

package org.eclipse.collections.api;

import java.util.Collection;
import java.util.NoSuchElementException;
<(wideStatisticsImport.(type))>

<(comparatorImports.(type))(type)>
import org.eclipse.collections.api.bag.primitive.Mutable<name>Bag;
import org.eclipse.collections.api.block.function.primitive.<name>ToBooleanFunction;
import org.eclipse.collections.api.block.function.primitive.<name>ToByteFunction;
import org.eclipse.collections.api.block.function.primitive.<name>ToShortFunction;
import org.eclipse.collections.api.block.function.primitive.<name>ToCharFunction;
import org.eclipse.collections.api.block.function.primitive.<name>ToDoubleFunction;
import org.eclipse.collections.api.block.function.primitive.<name>ToFloatFunction;
import org.eclipse.collections.api.block.function.primitive.<name>ToIntFunction;
import org.eclipse.collections.api.block.function.primitive.<name>ToLongFunction;
import org.eclipse.collections.api.block.function.primitive.<name>ToObjectFunction;
import org.eclipse.collections.api.block.function.primitive.Boolean<name>ToBooleanFunction;
import org.eclipse.collections.api.block.function.primitive.Byte<name>ToByteFunction;
import org.eclipse.collections.api.block.function.primitive.Char<name>ToCharFunction;
import org.eclipse.collections.api.block.function.primitive.Double<name>ToDoubleFunction;
import org.eclipse.collections.api.block.function.primitive.Float<name>ToFloatFunction;
import org.eclipse.collections.api.block.function.primitive.Int<name>ToIntFunction;
import org.eclipse.collections.api.block.function.primitive.Long<name>ToLongFunction;
import org.eclipse.collections.api.block.function.primitive.Object<name>ToObjectFunction;
import org.eclipse.collections.api.block.function.primitive.Short<name>ToShortFunction;
import org.eclipse.collections.api.block.predicate.primitive.<name>Predicate;
import org.eclipse.collections.api.block.procedure.primitive.<name>Procedure;
import org.eclipse.collections.api.collection.primitive.MutableBooleanCollection;
import org.eclipse.collections.api.collection.primitive.MutableByteCollection;
import org.eclipse.collections.api.collection.primitive.MutableCharCollection;
import org.eclipse.collections.api.collection.primitive.MutableDoubleCollection;
import org.eclipse.collections.api.collection.primitive.MutableFloatCollection;
import org.eclipse.collections.api.collection.primitive.MutableIntCollection;
import org.eclipse.collections.api.collection.primitive.MutableLongCollection;
import org.eclipse.collections.api.collection.primitive.MutableShortCollection;
import org.eclipse.collections.api.iterator.<name>Iterator;
import org.eclipse.collections.api.list.primitive.Mutable<name>List;
import org.eclipse.collections.api.set.primitive.<name>Set;
import org.eclipse.collections.api.set.primitive.Mutable<name>Set;

/**
 * <name>Iterable is an interface which is memory-optimized for <type> primitives.
 * It is inspired by the interface RichIterable, and contains a subset of the internal iterator methods on RichIterable like collect, sum, etc.
 * The API also includes an external iterator method, which returns an <name>Iterator. <name>Iterator helps iterate over the <name>Iterable without boxing the primitives.
 * This file was automatically generated from template file primitiveIterable.stg.
 */
public interface <name>Iterable extends PrimitiveIterable
{
    /**
     * Returns a primitive iterator that can be used to iterate over the <name>Iterable in an
     * imperative style.
     */
    <name>Iterator <type>Iterator();

    /**
     * Converts the <name>Iterable to a primitive <type> array.
     */
    <type>[] toArray();

    /**
     * Converts the <name>Iterable to a primitive <type> array. If the collection fits into the provided array it is used
     * to store its elements and is returned from the method, otherwise a new array of the appropriate size is allocated
     * and returned. If the iterable is empty, the target array is returned unchanged.
     */
    default <type>[] toArray(<type>[] target)
    {
        return this.toList().toArray(target);
    }

    /**
     * Returns true if the value is contained in the <name>Iterable, and false if it is not.
     */
    boolean contains(<type> value);

    /**
     * Returns true if all of the values specified in the source array are contained
     * in the <name>Iterable, and false if they are not.
     */
    default boolean containsAll(<type>... source)
    {
        if (this.size() \<= 32 || source.length \< 4)
        {
            for (<type> item : source)
            {
                if (!this.contains(item))
                {
                    return false;
                }
            }
            return true;
        }
        else
        {
            <name>Set set = this instanceof <name>Set ? (<name>Set) this : this.toSet();
            for (<type> item : source)
            {
                if (!set.contains(item))
                {
                    return false;
                }
            }
            return true;
        }
    }

    /**
     * Returns true if all of the values specified in the source <name>Iterable are contained
     * in the <name>Iterable, and false if they are not.
     */
    default boolean containsAll(<name>Iterable source)
    {
        if (this.size() \<= 32 || source.size() \< 4)
        {
            return source.allSatisfy(this::contains);
        }
        else
        {
            <name>Set set = this instanceof <name>Set ? (<name>Set) this : this.toSet();
            return source.allSatisfy(set::contains);
        }
    }

    /**
     * Returns true if any of the values specified in the source array are contained
     * in the <name>Iterable, and false if they are not.
     *
     * @since 11.0
     */
    default boolean containsAny(<type>... source)
    {
        <name>Iterable inside = this;
        if (this.size() > 32 && source.length > 32 && !(this instanceof <name>Set))
        {
            inside = this.toSet();
        }
        for (int i = 0; i \< source.length; i++)
        {
            if (inside.contains(source[i]))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if any of the values specified in the source <name>Iterable are contained
     * in the <name>Iterable, and false if they are not.
     *
     * @since 11.0
     */
    default boolean containsAny(<name>Iterable source)
    {
        <name>Iterable outside = this;
        <name>Iterable inside = source;
        if (this.size() \< source.size())
        {
            outside = source;
            inside = this;
        }
        if (outside instanceof <name>Set)
        {
            <name>Iterable temp = outside;
            outside = inside;
            inside = temp;
        }
        else if (inside.size() > 32 && !(inside instanceof <name>Set))
        {
            inside = inside.toSet();
        }
        return outside.anySatisfy(inside::contains);
    }

    /**
     * Returns true if none of the values specified in the source array are contained
     * in the <name>Iterable, and false if they are.
     *
     * @since 11.0
     */
    default boolean containsNone(<type>... source)
    {
        <name>Iterable inside = this;
        if (this.size() > 32 && source.length > 32 && !(this instanceof <name>Set))
        {
            inside = this.toSet();
        }
        for (int i = 0; i \< source.length; i++)
        {
            if (inside.contains(source[i]))
            {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns true if none of the values specified in the source <name>Iterable are contained
     * in the <name>Iterable, and false if they are.
     *
     * @since 11.0
     */
    default boolean containsNone(<name>Iterable source)
    {
        <name>Iterable outside = this;
        <name>Iterable inside = source;
        if (this.size() \< source.size())
        {
            outside = source;
            inside = this;
        }
        if (outside instanceof <name>Set)
        {
            <name>Iterable temp = outside;
            outside = inside;
            inside = temp;
        }
        else if (inside.size() > 32 && !(inside instanceof <name>Set))
        {
            inside = inside.toSet();
        }
        return outside.noneSatisfy(inside::contains);
    }

    /**
     * Applies the <name>Procedure to each element in the <name>Iterable.
     */
    default void forEach(<name>Procedure procedure)
    {
        this.each(procedure);
    }

    /**
     * A synonym for forEach.
     *
     * @since 7.0.
     */
    void each(<name>Procedure procedure);

    /**
     * @since 9.0.
     */
    default <name>Iterable tap(<name>Procedure procedure)
    {
        this.forEach(procedure);
        return this;
    }

    /**
     * Returns a new <name>Iterable with all of the elements in the <name>Iterable that
     * return true for the specified predicate.
     */
    <name>Iterable select(<name>Predicate predicate);

    /**
     * Returns a new <name>Iterable with all of the elements in the <name>Iterable that
     * return false for the specified predicate.
     */
    <name>Iterable reject(<name>Predicate predicate);

    /**
     * Same as {@link #select(<name>Predicate)} , only the results are added to the target Mutable<name>Collection.
     *
     * @since 8.1.
     */
    default \<R extends Mutable<name>Collection> R select(<name>Predicate predicate, R target)
    {
        this.each(each ->
        {
            if (predicate.accept(each))
            {
                target.add(each);
            }
        });
        return target;
    }

    /**
     * Same as {@link #reject(<name>Predicate)} , only the results are added to the target Mutable<name>Collection.
     *
     * @since 8.1.
     */
    default \<R extends Mutable<name>Collection> R reject(<name>Predicate predicate, R target)
    {
        this.each(each ->
        {
            if (!predicate.accept(each))
            {
                target.add(each);
            }
        });
        return target;
    }

    /**
     * Returns a new collection with the results of applying the specified function on each element of the source
     * collection.  This method is also commonly called transform or map.
     */
    \<V> RichIterable\<V> collect(<name>ToObjectFunction\<? extends V> function);

    /**
     * Same as {@link #collect(<name>ToObjectFunction)} , only the results are added to the target Collection.
     *
     * @since 8.1.
     */
    default \<V, R extends Collection\<V>\> R collect(<name>ToObjectFunction\<? extends V> function, R target)
    {
        this.each(each -> target.add(function.valueOf(each)));
        return target;
    }

    /**
     * {@code flatCollect} is a special case of {@link #collect(<name>ToObjectFunction)}. With {@code collect}, when the {@link <name>ToObjectFunction} returns
     * a collection, the result is a collection of collections. {@code flatCollect} outputs a single "flattened" collection
     * instead.  This method is commonly called flatMap.
     *
     * @since 8.1.
     */
    default \<V, R extends Collection\<V>\> R flatCollect(<name>ToObjectFunction\<? extends Iterable\<V>\> function, R target)
    {
        this.each(each ->
        {
            Iterable\<V> iterable = function.valueOf(each);
            if (iterable instanceof Collection)
            {
                target.addAll((Collection\<V>) iterable);
            }
            else
            {
                iterable.forEach(target::add);
            }
        });
        return target;
    }

    <collectPrimitive("Boolean")>

    <collectPrimitive("Byte")>

    <collectPrimitive("Char")>

    <collectPrimitive("Short")>

    <collectPrimitive("Int")>

    <collectPrimitive("Float")>

    <collectPrimitive("Long")>

    <collectPrimitive("Double")>

    <type> detectIfNone(<name>Predicate predicate, <type> ifNone);

    /**
     * Returns a count of the number of elements in the <name>Iterable that return true for the
     * specified predicate.
     */
    int count(<name>Predicate predicate);

    /**
     * Returns true if any of the elements in the <name>Iterable return true for the
     * specified predicate, otherwise returns false.
     */
    boolean anySatisfy(<name>Predicate predicate);

    /**
     * Returns true if all of the elements in the <name>Iterable return true for the
     * specified predicate, otherwise returns false.
     */
    boolean allSatisfy(<name>Predicate predicate);

    /**
     * Returns true if none of the elements in the <name>Iterable return true for the
     * specified predicate, otherwise returns false.
     */
    default boolean noneSatisfy(<name>Predicate predicate)
    {
        return !this.anySatisfy(predicate);
    }

    /**
     * Converts the <name>Iterable to a new Mutable<name>List.
     */
    Mutable<name>List toList();

    /**
     * Converts the <name>Iterable to a new Mutable<name>Set.
     */
    Mutable<name>Set toSet();

    /**
     * Converts the <name>Iterable to a new Mutable<name>Bag.
     */
    Mutable<name>Bag toBag();

    /**
     * Returns a Lazy<name>Iterable adapter wrapping the source <name>Iterable.
     */
    Lazy<name>Iterable asLazy();

    \<T> T injectInto(T injectedValue, Object<name>ToObjectFunction\<? super T, ? extends T> function);

    <injectIntoPrimitive("Boolean", "boolean")>

    <injectIntoPrimitive("Byte", "byte")>

    <injectIntoPrimitive("Char", "char")>

    <injectIntoPrimitive("Short", "short")>

    <injectIntoPrimitive("Int", "int")>

    <injectIntoPrimitive("Float", "float")>

    <injectIntoPrimitive("Long", "long")>

    <injectIntoPrimitive("Double", "double")>

    /**
     * @see #reduce(<(wideName.(type))><name>To<(wideName.(type))>Function)
     *
     * @since 10.0
     */
    default <(wideType.(type))> reduceIfEmpty(<(wideName.(type))><name>To<(wideName.(type))>Function accumulator, <(wideType.(type))> defaultValue)
    {
        if (this.isEmpty())
        {
            return defaultValue;
        }
        else
        {
            return this.reduce(accumulator);
        }
    }

    /**
     * @see RichIterable#reduce(BinaryOperator)
     *
     * @since 10.0
     */
    default <(wideType.(type))> reduce(<(wideName.(type))><name>To<(wideName.(type))>Function accumulator)
    {
        boolean[] seenOne = new boolean[1];
        <(wideType.(type))>[] result = new <(wideType.(type))>[1];
        this.each(each ->
        {
            if (seenOne[0])
            {
                result[0] = accumulator.valueOf(result[0], each);
            }
            else
            {
                seenOne[0] = true;
                result[0] = <castWideType.(type)>each;
            }
        });
        if (!seenOne[0])
        {
            throw new NoSuchElementException();
        }
        else
        {
            return result[0];
        }
    }

    /**
     * Partitions elements in fixed size chunks.
     *
     * @param size the number of elements per chunk
     *
     * @return A {@code RichIterable} containing {@code <name>Iterable}s of size {@code size}, except the last will be
     * truncated if the elements don't divide evenly.
     *
     * @since 9.2
     */
    default RichIterable\<<name>Iterable> chunk(int size)
    {
        throw new UnsupportedOperationException("Default method to prevent breaking backwards compatibility");
    }
    <(arithmeticMethods.(type))()>
}

>>

arithmeticMethods ::= [
    "byte": "allMethods",
    "short": "allMethods",
    "char": "allMethods",
    "int": "allMethods",
    "long": "allMethods",
    "float": "allMethods",
    "double": "allMethods",
    "boolean": "noMethods"
    ]

allMethods() ::= <<

<(wideType.(type))> sum();

/**
 * @since 8.0
 */
default <(wideStatistics.(type))> summaryStatistics()
{
    <(wideStatistics.(type))> stats = new <(wideStatistics.(type))>();
    this.forEach(stats::accept);
    return stats;
}

<type> max();

<type> maxIfEmpty(<type> defaultValue);

<type> min();

<type> minIfEmpty(<type> defaultValue);

double average();

/**
 * @since 9.0
 */
default double averageIfEmpty(double defaultValue)
{
    if (this.isEmpty())
    {
        return defaultValue;
    }
    return this.average();
}

double median();

/**
 * @since 9.0
 */
default double medianIfEmpty(double defaultValue)
{
    if (this.isEmpty())
    {
        return defaultValue;
    }
    return this.median();
}

<type>[] toSortedArray();

Mutable<name>List toSortedList();

/**
 * Converts the collection to a Mutable<name>List implementation sorted using the provided comparator.
 */
default Mutable<name>List toSortedList(<name>Comparator comparator)
{
    return this.toList().sortThis(comparator);
}

/**
 * Converts the collection to a Mutable<name>ListImplementation sorted based on the natural order of the key returned
 * by {@code function}.
 */
default \<T> Mutable<name>List toSortedListBy(<name>ToObjectFunction\<T> function)
{
    return this.toList().sortThisBy(function);
}

/**
 * Converts the collection to a Mutable<name>List implementation, which is sorted based on the key returned by
 * {@code function} using the provided {@code comparator}.
 */
default \<T> Mutable<name>List toSortedListBy(<name>ToObjectFunction\<T> function, Comparator\<? super T> comparator)
{
    return this.toList().sortThisBy(function, comparator);
}
>>

noMethods() ::= ""

wideStatistics ::= [
    "byte": "IntSummaryStatistics",
    "short": "IntSummaryStatistics",
    "char": "IntSummaryStatistics",
    "int": "IntSummaryStatistics",
    "long": "LongSummaryStatistics",
    "float": "DoubleSummaryStatistics",
    "double": "DoubleSummaryStatistics",
    default: "no matching wide type"
]

wideStatisticsImport ::= [
    "boolean": "",
    "byte": "import java.util.IntSummaryStatistics;",
    "short": "import java.util.IntSummaryStatistics;",
    "char": "import java.util.IntSummaryStatistics;",
    "int": "import java.util.IntSummaryStatistics;",
    "long": "import java.util.LongSummaryStatistics;",
    "float": "import java.util.DoubleSummaryStatistics;",
    "double": "import java.util.DoubleSummaryStatistics;",
    default: "no matching wide type"
]

comparatorImports ::= [
    "byte": "allComparatorImports",
    "short": "allComparatorImports",
    "char": "allComparatorImports",
    "int": "allComparatorImports",
    "long": "allComparatorImports",
    "float": "allComparatorImports",
    "double": "allComparatorImports",
    "boolean": "noComparatorImports"
]

allComparatorImports(type) ::= <<
import java.util.Comparator;
import org.eclipse.collections.api.block.comparator.primitive.<name>Comparator;

>>

noComparatorImports(type) ::= ""

injectIntoPrimitive(toName, toType) ::= <<
/**
 * Returns the final <toType> result of evaluating function using each element of the iterable and the previous evaluation
 * result as the parameters. The injected value is used for the first parameter of the first evaluation, and the current
 * item in the iterable is used as the second parameter.
 *
 * @since 11.1
 */
default <toType> injectInto<toName>(<toType> injectedValue, <toName><name>To<toName>Function function)
{
    <toType>[] result = new <toType>[1];
    result[0] = injectedValue;
    this.each(each ->
    {
        result[0] = function.valueOf(result[0], each);
    });
    return result[0];
}
>>

collectPrimitive(toName) ::= <<
/**
 * Returns the target {@code Mutable<toName>Collection} with the results of applying the specified function on each element
 * of the source collection.
 *
 * @since 8.1.
 */
default \<R extends Mutable<toName>Collection> R collect<toName>(<name>To<toName>Function function, R target)
{
    this.each(each -> target.add(function.valueOf(each)));
    return target;
}
>>
