001/*
002 * Copyright 2025 devteam@scivicslab.com
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing,
011 * software distributed under the License is distributed on an
012 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied.  See the License for the
014 * specific language governing permissions and limitations
015 * under the License.
016 */
017
018package com.scivicslab.actoriac.accumulator;
019
020import java.io.PrintStream;
021import java.util.concurrent.atomic.AtomicInteger;
022
023import com.scivicslab.pojoactor.core.accumulator.Accumulator;
024
025/**
026 * Accumulator that outputs to the console (System.out/System.err).
027 *
028 * <p>This accumulator writes output directly to the console as it arrives.
029 * It supports different output types:</p>
030 * <ul>
031 *   <li>{@code cowsay} - Rendered cowsay ASCII art (output as-is)</li>
032 *   <li>{@code stdout} - Command stdout (output as-is)</li>
033 *   <li>{@code stderr} - Command stderr (output to System.err)</li>
034 * </ul>
035 *
036 * <h2>Usage</h2>
037 * <pre>{@code
038 * ConsoleAccumulator console = new ConsoleAccumulator();
039 * console.add("node-1", "stdout", "command output line");
040 * console.add("workflow", "cowsay", renderedCowsayArt);
041 * }</pre>
042 *
043 * @author devteam@scivicslab.com
044 * @since 2.12.0
045 */
046public class ConsoleAccumulator implements Accumulator {
047
048    private final PrintStream stdout;
049    private final PrintStream stderr;
050    private final AtomicInteger count = new AtomicInteger(0);
051    private volatile boolean quiet = false;
052
053    /**
054     * Constructs a ConsoleAccumulator with default System.out and System.err.
055     */
056    public ConsoleAccumulator() {
057        this(System.out, System.err);
058    }
059
060    /**
061     * Constructs a ConsoleAccumulator with custom output streams.
062     *
063     * @param stdout the stream for stdout output
064     * @param stderr the stream for stderr output
065     */
066    public ConsoleAccumulator(PrintStream stdout, PrintStream stderr) {
067        this.stdout = stdout;
068        this.stderr = stderr;
069    }
070
071    /**
072     * Sets quiet mode. When quiet, no output is written.
073     *
074     * @param quiet true to suppress output, false to enable output
075     */
076    public void setQuiet(boolean quiet) {
077        this.quiet = quiet;
078    }
079
080    /**
081     * Returns whether quiet mode is enabled.
082     *
083     * @return true if quiet mode is enabled
084     */
085    public boolean isQuiet() {
086        return quiet;
087    }
088
089    @Override
090    public void add(String source, String type, String data) {
091        if (quiet) {
092            count.incrementAndGet();
093            return;
094        }
095
096        if (data == null || data.isEmpty()) {
097            count.incrementAndGet();
098            return;
099        }
100
101        // Format output with fixed-width source prefix on each line
102        String output = formatOutput(source, data);
103
104        switch (type) {
105            case "stderr":
106                stderr.print(output);
107                break;
108            case "cowsay":
109            case "stdout":
110            default:
111                stdout.print(output);
112                break;
113        }
114        count.incrementAndGet();
115    }
116
117    /**
118     * Formats the output with a fixed-width source prefix on each line.
119     *
120     * <p>Every line of output is prefixed with {@code [source]} where source
121     * is left-justified in a fixed-width field. This allows multi-line output
122     * (such as cowsay ASCII art) to remain properly aligned while still being
123     * identifiable by source.</p>
124     *
125     * <p>Example output:</p>
126     * <pre>
127     * [node-web-01    ] command output here
128     * [workflow       ]  _______________________
129     * [workflow       ] < Starting workflow... >
130     * [workflow       ]  -----------------------
131     * </pre>
132     *
133     * @param source the source identifier (e.g., "node-web-01", "cli")
134     * @param data the output data (may contain multiple lines)
135     * @return the formatted output string with prefix on each line
136     */
137    private String formatOutput(String source, String data) {
138        String prefix = formatPrefix(source);
139        StringBuilder sb = new StringBuilder();
140
141        String[] lines = data.split("\n", -1);
142        for (int i = 0; i < lines.length; i++) {
143            sb.append(prefix).append(lines[i]);
144            if (i < lines.length - 1) {
145                sb.append("\n");
146            }
147        }
148        sb.append("\n");
149
150        return sb.toString();
151    }
152
153    /**
154     * Creates a prefix from the source name.
155     *
156     * @param source the source identifier
157     * @return formatted prefix like "[node-web-01] "
158     */
159    private String formatPrefix(String source) {
160        String src = (source != null) ? source : "";
161        return "[" + src + "] ";
162    }
163
164    @Override
165    public String getSummary() {
166        return "ConsoleAccumulator: " + count.get() + " entries written to console";
167    }
168
169    @Override
170    public int getCount() {
171        return count.get();
172    }
173
174    @Override
175    public void clear() {
176        count.set(0);
177    }
178}