001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package dev.enola.common.linereader;
019
020import static java.nio.charset.StandardCharsets.UTF_8;
021
022import com.google.common.collect.ImmutableMap;
023
024import org.jspecify.annotations.Nullable;
025
026import java.io.*;
027import java.nio.charset.Charset;
028import java.util.List;
029
030public class TestIO implements IO {
031
032    static final Charset CHARSET = UTF_8;
033
034    private final BufferedReader inputReader;
035    private final ByteArrayInputStream input;
036    private final ByteArrayOutputStream output = new ByteArrayOutputStream();
037    private final ExecutionContext ctx;
038
039    public TestIO(List<String> lines) {
040        var text = String.join("\n", lines);
041        var bytes = text.getBytes(CHARSET);
042        this.input = new ByteArrayInputStream(bytes);
043        this.inputReader = new BufferedReader(new InputStreamReader(input, CHARSET));
044
045        this.ctx =
046                new ExecutionContext() {
047                    @Override
048                    public ImmutableMap<String, String> environment() {
049                        return ImmutableMap.of();
050                    }
051
052                    @Override
053                    public InputStream input() {
054                        return input;
055                    }
056
057                    @Override
058                    public OutputStream output() {
059                        return output;
060                    }
061
062                    @Override
063                    public OutputStream error() {
064                        return output;
065                    }
066
067                    @Override
068                    public Charset inputCharset() {
069                        return CHARSET;
070                    }
071
072                    @Override
073                    public Charset outputCharset() {
074                        return CHARSET;
075                    }
076
077                    @Override
078                    public Charset errorCharset() {
079                        return CHARSET;
080                    }
081                };
082    }
083
084    @Override
085    public ExecutionContext ctx() {
086        return ctx;
087    }
088
089    @Override
090    public @Nullable String readLine() {
091        // if (input.available() < 1) return null;
092        try {
093            if (!inputReader.ready()) return null;
094            return inputReader.readLine();
095        } catch (IOException ex) {
096            throw new UncheckedIOException(ex);
097        }
098    }
099
100    @Override
101    public @Nullable String readLine(String prompt) {
102        printf(prompt);
103        return readLine();
104    }
105
106    @Override
107    public void printf(String format, Object... args) {
108        output.writeBytes(String.format(format, args).getBytes(ctx.outputCharset()));
109    }
110
111    // This signature is misleading, because it makes it appear as if we were tracking "line",
112    // whereas of course this is actually not so; any notion of print-by-line is meaningless
113    // and lost as soon as any printf() contains new line characters.
114    //   public List<String> getOutput() {
115    //     return  output.toString(ctx.outputCharset()).lines().toList(); }
116
117    public String getOutput() {
118        return output.toString(ctx.outputCharset());
119    }
120}