001/*
002 * Copyright 2025 devteam@scivics-lab.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;
019
020import java.util.List;
021
022import com.github.ricksbrown.cowsay.Cowsay;
023import com.scivicslab.actoriac.log.DistributedLogStore;
024import com.scivicslab.actoriac.log.LogLevel;
025import com.scivicslab.pojoactor.workflow.IIActorSystem;
026import com.scivicslab.pojoactor.workflow.Interpreter;
027import com.scivicslab.pojoactor.workflow.Vertex;
028
029/**
030 * Level 3 wrapper that adds workflow capabilities to a NodeGroup POJO.
031 *
032 * <p>This class extends {@link Interpreter} to provide workflow execution
033 * capabilities while delegating node group operations to a wrapped {@link NodeGroup} instance.</p>
034 *
035 * <p>This follows the same three-level architecture as NodeInterpreter:</p>
036 * <ul>
037 * <li><strong>Level 1 (POJO):</strong> {@link NodeGroup} - pure POJO with inventory management</li>
038 * <li><strong>Level 2 (Actor):</strong> ActorRef&lt;NodeGroup&gt; - actor wrapper for concurrent execution</li>
039 * <li><strong>Level 3 (Workflow):</strong> NodeGroupInterpreter - workflow capabilities + IIActorRef wrapper</li>
040 * </ul>
041 *
042 * <p><strong>Design principle:</strong> NodeGroup remains a pure POJO, independent of ActorSystem.
043 * NodeGroupInterpreter wraps NodeGroup to add workflow capabilities without modifying the NodeGroup class.</p>
044 *
045 * @author devteam@scivics-lab.com
046 */
047public class NodeGroupInterpreter extends Interpreter {
048
049    /**
050     * The wrapped NodeGroup POJO that handles inventory and node creation.
051     */
052    private final NodeGroup nodeGroup;
053
054    /**
055     * The overlay directory path for YAML overlay feature.
056     * When set, workflows are loaded with overlay applied.
057     */
058    private String overlayDir;
059
060    /**
061     * Verbose output flag.
062     * When true, displays full YAML for each vertex instead of truncated version.
063     */
064    private boolean verbose = false;
065
066    /**
067     * Distributed log store for structured logging.
068     */
069    private DistributedLogStore logStore;
070
071    /**
072     * Session ID for the current workflow execution.
073     */
074    private long sessionId = -1;
075
076    /**
077     * Constructs a NodeGroupInterpreter that wraps the specified NodeGroup.
078     *
079     * @param nodeGroup the {@link NodeGroup} instance to wrap
080     * @param system the actor system for workflow execution
081     */
082    public NodeGroupInterpreter(NodeGroup nodeGroup, IIActorSystem system) {
083        super();
084        this.nodeGroup = nodeGroup;
085        this.system = system;
086    }
087
088    /**
089     * Creates Node objects for all hosts in the specified group.
090     *
091     * <p>Delegates to the wrapped {@link NodeGroup#createNodesForGroup(String)} method.</p>
092     *
093     * @param groupName the name of the group from the inventory file
094     * @return the list of created Node objects
095     */
096    public List<Node> createNodesForGroup(String groupName) {
097        return nodeGroup.createNodesForGroup(groupName);
098    }
099
100    /**
101     * Creates a single Node for localhost execution.
102     *
103     * <p>Delegates to the wrapped {@link NodeGroup#createLocalNode()} method.</p>
104     *
105     * @return a list containing a single localhost Node
106     */
107    public List<Node> createLocalNode() {
108        return nodeGroup.createLocalNode();
109    }
110
111    /**
112     * Gets the inventory object.
113     *
114     * @return the loaded inventory, or null if not loaded
115     */
116    public InventoryParser.Inventory getInventory() {
117        return nodeGroup.getInventory();
118    }
119
120    /**
121     * Gets the wrapped NodeGroup instance.
122     *
123     * <p>This allows direct access to the underlying POJO when needed.</p>
124     *
125     * @return the wrapped NodeGroup
126     */
127    public NodeGroup getNodeGroup() {
128        return nodeGroup;
129    }
130
131    /**
132     * Sets the overlay directory for YAML overlay feature.
133     *
134     * <p>When an overlay directory is set, workflows will be loaded with
135     * overlay applied, allowing environment-specific configuration.</p>
136     *
137     * @param overlayDir the path to the overlay directory containing overlay-conf.yaml
138     */
139    public void setOverlayDir(String overlayDir) {
140        this.overlayDir = overlayDir;
141    }
142
143    /**
144     * Gets the overlay directory path.
145     *
146     * @return the overlay directory path, or null if not set
147     */
148    public String getOverlayDir() {
149        return overlayDir;
150    }
151
152    /**
153     * Sets verbose mode for detailed output.
154     *
155     * <p>When enabled, displays full YAML for each vertex in cowsay output
156     * instead of the truncated version.</p>
157     *
158     * @param verbose true to enable verbose output
159     */
160    public void setVerbose(boolean verbose) {
161        this.verbose = verbose;
162    }
163
164    /**
165     * Checks if verbose mode is enabled.
166     *
167     * @return true if verbose mode is enabled
168     */
169    public boolean isVerbose() {
170        return verbose;
171    }
172
173    /**
174     * Sets the distributed log store for structured logging.
175     *
176     * @param logStore the log store to use
177     * @param sessionId the session ID for this execution
178     */
179    public void setLogStore(DistributedLogStore logStore, long sessionId) {
180        this.logStore = logStore;
181        this.sessionId = sessionId;
182    }
183
184    /**
185     * Gets the log store.
186     *
187     * @return the log store, or null if not set
188     */
189    public DistributedLogStore getLogStore() {
190        return logStore;
191    }
192
193    /**
194     * Gets the session ID.
195     *
196     * @return the session ID, or -1 if not set
197     */
198    public long getSessionId() {
199        return sessionId;
200    }
201
202    /**
203     * Hook called when entering a vertex during workflow execution.
204     *
205     * <p>Displays the workflow name and vertex definition using cowsay.
206     * In normal mode, shows first 10 lines. In verbose mode, shows the full YAML
207     * after the cowsay output.</p>
208     *
209     * @param vertex the vertex being entered
210     */
211    @Override
212    protected void onEnterVertex(Vertex vertex) {
213        // Get workflow name
214        String workflowName = (getCode() != null && getCode().getName() != null)
215                ? getCode().getName()
216                : "unknown-workflow";
217
218        // Get YAML-formatted output (first 10 lines for cowsay)
219        String yamlText = vertex.toYamlString(10).trim();
220
221        // Combine workflow name and vertex YAML
222        String displayText = "[" + workflowName + "]\n" + yamlText;
223        String[] cowsayArgs = { displayText };
224        System.out.println(Cowsay.say(cowsayArgs));
225
226        // In verbose mode, show the full YAML after cowsay
227        if (verbose) {
228            String fullYaml = vertex.toYamlString(-1);
229            System.out.println("--- Full vertex YAML ---");
230            System.out.println(fullYaml);
231            System.out.println("------------------------");
232        }
233
234        // Log to distributed log store
235        if (logStore != null && sessionId >= 0) {
236            String vertexName = vertex.getVertexName();
237            if (vertexName == null && vertex.getStates() != null && vertex.getStates().size() >= 2) {
238                vertexName = vertex.getStates().get(0) + " -> " + vertex.getStates().get(1);
239            }
240            logStore.log(sessionId, "nodeGroup", vertexName, LogLevel.INFO,
241                    "Entering vertex: " + yamlText.split("\n")[0]);
242        }
243    }
244}