001/*
002 * JDrupes Builder
003 * Copyright (C) 2026 Michael N. Lipp
004 * 
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
017 */
018
019package org.jdrupes.builder.core;
020
021import java.io.InputStream;
022import java.io.OutputStream;
023import java.nio.file.Path;
024import java.util.Objects;
025import java.util.function.BiConsumer;
026import java.util.stream.Stream;
027import org.jdrupes.builder.api.FileTree;
028import org.jdrupes.builder.api.Project;
029import org.jdrupes.builder.api.Resource;
030import org.jdrupes.builder.api.ResourceRequest;
031import org.jdrupes.builder.api.ResourceType;
032import static org.jdrupes.builder.api.ResourceType.CleanlinessType;
033import org.jdrupes.builder.api.Resources;
034
035/// The Class Copier.
036///
037public class Copier extends AbstractGenerator {
038
039    /// A source description.
040    ///
041    /// @param tree the tree to copy
042    /// @param subdir a subdirectory of the destination set with [#into]
043    /// @param filter the filter
044    ///
045    public record Source(FileTree<?> tree, Path subdir,
046            BiConsumer<InputStream, OutputStream> filter) {
047    }
048
049    private final StreamCollector<Source> sources
050        = new StreamCollector<>(true);
051    private Path destination;
052    private ResourceRequest<?> requestForCopied;
053
054    /// Initializes a new copier.
055    ///
056    /// @param project the project
057    ///
058    public Copier(Project project) {
059        super(project);
060    }
061
062    /// Add the given [Stream] of Sources to the source resources.
063    ///
064    /// @param sources the sources
065    /// @return the copier
066    ///
067    public Copier source(Stream<Source> sources) {
068        this.sources.add(sources);
069        return this;
070    }
071
072    /// Convenience method to add a [FileTree] without subdirectory or
073    /// filter to the sources. If `root` is a relative path, it is resolved
074    /// against the project's directory.
075    ///
076    /// @param root the root
077    /// @param pattern the pattern
078    /// @return the copier
079    ///
080    public Copier source(Path root, String pattern) {
081        sources.add(Stream.of(new Source(FileTree.from(
082            project(), root, pattern), Path.of("."), null)));
083        return this;
084    }
085
086    /// Sets the destination directory for the copied resources. If the
087    /// destination is relative, it is resolved against the project's
088    /// directory.
089    ///
090    /// @param destination the destination
091    /// @return the copier
092    ///
093    public Copier into(Path destination) {
094        if (!destination.isAbsolute()) {
095            destination = project().directory().resolve(destination);
096        }
097        if (!destination.toFile().isDirectory()) {
098            throw new IllegalArgumentException("Argument must be a directory.");
099        }
100        this.destination = destination;
101        return this;
102    }
103
104    /// Provide the generated resources in response to a request like
105    /// the given one.
106    ///
107    /// @param proto defines the kind of request that the copier
108    /// should respond to with the copied resources
109    /// @return the copier
110    ///
111    public Copier provideResources(ResourceRequest<?> proto) {
112        requestForCopied = proto;
113        return this;
114    }
115
116    @Override
117    protected <T extends Resource> Stream<T>
118            doProvide(ResourceRequest<T> request) {
119        if (request.accepts(CleanlinessType)) {
120            // TODO
121            return Stream.empty();
122        }
123
124        // Handle request for generated resources
125        if (requestForCopied != null
126            && request.accepts(requestForCopied.type())
127            && (requestForCopied.name().isEmpty()
128                || Objects.equals(requestForCopied.name().get(),
129                    request.name().orElse(null)))) {
130            // Always evaluate for most special type
131            if (!request.equals(requestForCopied)) {
132                @SuppressWarnings({ "unchecked", "PMD.AvoidDuplicateLiterals" })
133                var result = (Stream<T>) resources(requestForCopied);
134                return result;
135            }
136        }
137
138        if (destination == null) {
139            throw new IllegalStateException("No destination set.");
140        }
141
142        // Make sure that the required resources are retrieved and exist
143        var required = sources.stream().toList();
144
145        return Stream.empty();
146    }
147
148}