package org.implab.gradle.common.exec.runtime;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Scanner;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
 * Redirection specification for the {@link InputStream}. Redirection is invoked
 * when the {@link InputStream} becomes available, for example, on process
 * start. Before the process is started the redirection isn't invoked and no
 * resources are allocated or used.
 */
@NonNullByDefault
public interface RedirectTo {
    CompletableFuture<Void> redirect(InputStream from);

    public interface StringConsumer extends Consumer<String> {
        default void complete() {
        }
    }

    /** Creates a redirect to the specified consumer */
    public static RedirectTo eachLine(final Consumer<String> consumer) {
        return consumer(new StringConsumer() {
            @Override
            public void accept(String s) {
                consumer.accept(s);
            }
        });
    }

    public static RedirectTo allText(final Consumer<String> consumer) {
        return consumer(new StringConsumer() {
            final Stream.Builder<String> builder = Stream.builder();

            @Override
            public void accept(String s) {
                builder.accept(s);
            }

            @Override
            public void complete() {
                consumer.accept(builder.build().collect(Collectors.joining("\n")));
            }
        });
    }

    /** Creates a redirect to the specified consumer */
    public static RedirectTo consumer(final StringConsumer consumer) {
        return (src) -> CompletableFuture.runAsync(() -> {
            try (Scanner sc = new Scanner(src)) {
                while (sc.hasNextLine()) {
                    consumer.accept(sc.nextLine());
                }
                consumer.complete();
            }
        });
    }

    /**
     * Creates a redirect to the specified file. There will be opened the output
     * stream in this redirection and original stream will be transferred to this
     * this output stream.
     */
    public static RedirectTo file(final File file) {
        return src -> CompletableFuture.runAsync(() -> {
            try (src; OutputStream out = new FileOutputStream(file)) {
                src.transferTo(out);
            } catch (Exception e) {
                // silence!
            }
        });
    }

    /** Creates a redirect to the specified output stream. */
    public static RedirectTo stream(final OutputStream dest) {
        return src -> CompletableFuture.runAsync(() -> {
            try (dest; src) {
                src.transferTo(dest);
            } catch (Exception e) {
                // silence!
            }
        });
    }

    /**
     * Creates the redirection to the specified destination, actual type of
     * redirection will be determined from the type of the output object.
     */
    public static RedirectTo any(final Object output) {
        if (output instanceof StringConsumer fn) {
            return consumer(s -> fn.accept(s));
        } else if (output instanceof File f) {
            return file(f);
        } else if (output instanceof OutputStream stm) {
            return stream(stm);
        } else if (output instanceof RedirectTo self) {
            return self;
        } else {
            throw new IllegalArgumentException("The specified argument type isn't supported: " + output.getClass());
        }
    }
}
