<?php

// Dependency management
// based loosely on https://www.electricmonk.nl/log/2008/08/07/dependency-resolving-algorithm/

class dependency_node {
	
	private $resolved;
	private $unresolved;
	public $name;
	public $edges;
	public $payload;
	
	function __construct($name, $object) {
		$this->name = $name;
		$this->edges = [];
		$this->resolved = 0;
		$this->unresolved = 0;
		$this->payload = $object;
	}
	function addEdge($node) {		// $this->addEdge($node).   '$this' depends on '$node'
		$this->edges[] = $node;
	}
	function resolved() {
		if (func_num_args()) {
			$this->resolved = func_get_arg(0);
			return $this;
		} else {
			return $this->resolved;
		}
	}
	function unresolved() {
		if (func_num_args()) {
			$this->unresolved = func_get_arg(0);
			return $this;
		} else {
			return $this->unresolved;
		}
	}
}

class DependencyManager {
	
	private $main;
	private $dependencycheck = TRUE;
	
	function __construct() {
		$this->main = new dependency_node('*', NULL);		// main "dependency" depends on everything
	}
	
	public function Add($object) {		// $object->tag = name of module,  $object->depends = array of modules it depends on
		$name = $object->tag();
		if ($name == '') $name = 'node' . mt_rand(100000, 999999);

		$node = new dependency_node($name, $object);
		$this->main->addEdge($node);
		$object->x_node = $node;
	}
	
	function getNodeByTag($tag) {
		foreach($this->main->edges as $edge) {
			if ($edge->name == $tag) return $edge;
		}
		return NULL;
	}
	
	// now that all objects have been added, we can look at the dependencies
	private function _AddDependencies() {
		foreach($this->main->edges as $source_node) {
			$depends = $source_node->payload->depends();
			foreach($depends as $depends_on) {
				$tag = $source_node->payload->tag();
				if ($depends_on != $tag) {
					$parent = $this->getNodeByTag($depends_on);
					if ($parent !== NULL) {
						$source_node->addEdge($parent);
					}
				}
			}
		}
	}
	
	private function _resolve($node, &$resolved) {
		$node->unresolved(TRUE);
		foreach($node->edges as $edge) {
			if (!$edge->resolved()) {
			if ($edge->unresolved()) die("\ncircular reference: {$node->name} -> {$edge->name}\n");
				$this->_resolve($edge, $resolved);
			}
		}
		$node->resolved(TRUE)->unresolved(FALSE);
		$resolved[] = $node;
	}

	public function Resolve() {
		// add dependencies
		if ($this->dependencycheck) {
			$this->_AddDependencies();
			$this->dependencycheck = FALSE;
		}
		
		// Resolve
		$resolved = [];
		$this->_resolve($this->main, $resolved);
		$output = [];
		foreach($resolved as $node) {
			$output[] = $node->payload;
		}
		return $output;
	}
}