/*
 * 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.jena.atlas.lib;

import java.io.PrintStream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.jena.atlas.iterator.Iter;

/**
 * Collect some stream operations into one place.
 * Sometimes, the function form reads better.
 * @see Iter Iter - a stream-like class for iterators.
 */
public class StreamOps {
    /**
     * Iterator to Stream.
     * Call to {@linkplain Iter#asStream}.
     */
    public static <X> Stream<X> stream(Iterator<X> iter) {
        return Iter.asStream(iter);
    }

//    /** Stream to {@link List} */
//    public static <X> List<X> toList(Stream<X> stream) {
//        return stream.toList();
//    }

    /** Stream to {@link Set} */
    public static <X> Set<X> toSet(Stream<X> stream) {
        return stream.collect(Collectors.toSet());
    }

    /** First element or null */
    public static <X> X first(Stream<X> stream) {
        return stream.findFirst().orElse(null);
    }

    /** An element from a {@link Collection} */
    public static <X> X element(Collection<X> collection) {
        return first(collection.stream());
    }

    /**
     * Join two streams. This is the function to accumulate stream from a loop.
     * If either stream is null, then return the other which may also be null.
     */
    public static <X> Stream<X> concat(Stream<X> stream1, Stream<X> stream2) {
        if ( stream1 == null )
            return stream2;
        if ( stream2 == null )
            return stream1;
        return Stream.concat(stream1, stream2);
    }

    /**
     * Join streams. This is the function to accumulate stream from a loop.
     * If either stream is null, then return the other which may also be null.
     */
    @SafeVarargs
    public static <X> Stream<X> concatAll(Stream<X> ... streams) {
        Stream<X> x = null;
        for ( Stream<X> stream : streams ) {
            if ( x == null )
                x = stream;
            else
                x = Stream.concat(x, stream);
        }
        return x;
    }

    /** Debug : print stream.
     * This operation prints the whole stream at the point it is used,
     * and then returns a new stream of the same elements.
     */
    public static <X> Stream<X> print(Stream<X> stream) {
        return print(System.out, stream);
    }

    public static <X> Stream<X> print(PrintStream out, Stream<X> stream) {
        stream = stream.map(item -> { out.println(item); return item; });
        return stream.toList().stream();
    }

    public static <X> Stream<X> print(PrintStream out, String leader, Stream<X> stream) {
        String prefix = (leader==null) ? "" : leader;
        stream = stream.map(item -> { out.print(prefix); out.println(item); return item; });
        return stream.toList().stream();
    }

    /** Print immediate, noting empty streams */
    public static <X> Stream<X> debug(Stream<X> stream) {
        List<X> elts = stream.toList();
        if ( elts.isEmpty() )
            System.out.println("[empty]");
        else {
            StringJoiner sj = new StringJoiner("\n  ", "[\n  ", "\n]");
            elts.forEach(b->sj.add(b.toString()));
            System.out.println(sj.toString());
        }
        return elts.stream();
    }
}
