/*-
 * ~~~~~~licensing~~~~~~
 * object-producing-shell
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.platform.core.producer.shell;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.karaf.shell.api.action.Action;
import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.Option;
import org.apache.karaf.shell.api.action.lifecycle.Reference;
import org.apache.karaf.shell.api.action.lifecycle.Service;
import org.apache.karaf.shell.support.table.ShellTable;

import ru.entaxy.platform.base.objects.factory.EntaxyFactory;
import ru.entaxy.platform.base.objects.factory.tracker.TrackedFactoryManager;
import ru.entaxy.platform.base.objects.factory.tracker.TrackedFactoryManager.TrackedManagedFactory;
import ru.entaxy.platform.base.support.CommonUtils;

@Service
@Command(scope = EntaxyProducerServiceSupport.SCOPE, name = "factory-manager-status")
public class FactoryManagerStatus implements Action {

	@Reference
	TrackedFactoryManager factoryManager;
	
	@Option(name = "-t", aliases = {"--tree"}, description = "Display factory tree")
	boolean asTree; 
	
	protected String getDisplayName(TrackedManagedFactory tmf) {
		try {
			return tmf.jsonFinal.get(EntaxyFactory.CONFIGURATION.FACTORY_SECTION_NAME)
					.getAsJsonObject()
					.get(EntaxyFactory.CONFIGURATION.FACTORY.DISPLAY_NAME)
					.getAsString();
		} catch (Exception e) {
			return "";
		}
	}
	
	@Override
	public Object execute() throws Exception {
		
		ShellTable table = new ShellTable();
		table.column("ID");
		table.column("Display Name");
		table.column("Active");
		table.column("Consistent");
		table.column("Up2date");
		table.column("Parent");
		table.column("Requires");
		
		if (!asTree) { 
			for (TrackedManagedFactory tmf: factoryManager.getManagedFactories()) {
				final List<String> waiting = tmf.waitingFor;
				table.addRow().addContent(
						tmf.factoryId
						, getDisplayName(tmf)
						, tmf.isActive?"*":""
						, tmf.isConsistent()?"*":""
						, tmf.isUpToDate?"*":""
						, decorate(tmf.parent, waiting)
						, tmf.requirements.stream().map(s -> decorate(s, waiting))
							.collect(Collectors.joining(","))
					);
				
			}
		} else {
			System.out.println(" TREE ");
			FactoryTree tree = new FactoryTree();
			tree.load(factoryManager.getManagedFactories());
			for (FactoryTreeNode node: tree.root.children)
				addRowToTable(table, node);
		}
		
		// OUTPUT TO SHELL
		table.print(System.out);
		
		return null;
	}

	protected String getLevelPrefix(FactoryTreeNode node) {
		String result = "";
		for (int i=0; i<node.getLevel()-1; i++)
			result = result.concat("  ");
		return result;
	}
	
	protected void addRowToTable(ShellTable table, FactoryTreeNode node) {
		if (node.factory == null) {
			table.addRow().addContent(
					getLevelPrefix(node) + node.factoryId
					, ""
					, ""
					, ""
					, ""
					, ""
					, ""
				);
			
		} else {
			TrackedManagedFactory tmf = node.factory;
			final List<String> waiting = tmf.waitingFor;
			table.addRow().addContent(
				getLevelPrefix(node) + tmf.factoryId
				, getDisplayName(tmf)
				, tmf.isActive?"*":""
				, tmf.isConsistent()?"*":""
				, tmf.isUpToDate?"*":""
				, decorate(tmf.parent, waiting)
				, tmf.requirements.stream().map(s -> decorate(s, waiting))
					.collect(Collectors.joining(","))
			);
		}
		for (FactoryTreeNode child: node.children) {
			addRowToTable(table, child);
		}
	}
	
	protected String decorate(String test, List<String> values) {
		if (values.contains(test))
			return "*" + test;
		return test;
	}
	
	protected class FactoryTree {
		
		Map<String, FactoryTreeNode> allNodes = new HashMap<>();
		
		FactoryTreeNode root = new FactoryTreeNode("@root");
		
		public void load(List<TrackedManagedFactory> factories) {
			
			for (TrackedManagedFactory tmf: factories) {
				FactoryTreeNode node = get(tmf.factoryId);
				node.factory = tmf;
				if (CommonUtils.isValid(tmf.parent))
					get(tmf.parent).addChild(node);
			}
			
		}
		
		public FactoryTreeNode get(String id) {
			if (!allNodes.containsKey(id)) {
				FactoryTreeNode node = new FactoryTreeNode(id);
				root.addChild(node);
				allNodes.put(id, node);
			}
			return allNodes.get(id);
		}
		
	}
	
	protected class FactoryTreeNode {
		
		String factoryId;
		TrackedManagedFactory factory;
		
		List<FactoryTreeNode> children = new ArrayList<>();
		
		FactoryTreeNode parent;
		
		public FactoryTreeNode (TrackedManagedFactory factory) {
			this.factory = factory;
			this.factoryId = this.factory.factoryId;
		}

		public FactoryTreeNode (String factoryId) {
			this.factoryId = factoryId;
		}
		
		public void addChild(FactoryTreeNode child) {
			this.children.add(child);
			if (child.parent != null)
				child.parent.children.remove(child);
			child.parent = this;
		}
		
		public int getLevel() {
			if (parent == null)
				return 0;
			return parent.getLevel() + 1;
		}
	}
	
}
