<?php
    ERROR_REPORTING(-1);
    
class Node {

    protected $root = NULL;
    protected $seed = array();
    protected $name;
    

    public function __construct($name) {
        $this->name = $name;
    }
    public function getName() {
        return $this->name;
    }
    
    public function getParentNode(){ 
        if (isset($this->root)) {
            return $this->root;
        }
        return NULL;
    }
    public function getChildrenNode() {
        return $this->seed;
    }
    
    public function getAllParents(){
        if (NULL == $this->getParentNode()) {
            return array();
        } else {
            $parent = $this->getParentNode();
            $parents = $parent->getAllParents();
            $parents[] = $parent;
            return $parents;
        }
    }
    public function getAllChildren(){
        $childrenNode = $this->getChildrenNode();
        $children = $childrenNode;
        foreach ($childrenNode as $child) {
            $children = array_merge($children, $child->getAllChildren());
        }
        
        return $children;
    }
    
    public function getDepth() {
        $seeds = $this->seed;
        $depth = -1;
        //echo $this->name() . '->';
        foreach ($seeds as $seed) {
            $seedDepth = $seed->getDepth();
            if ($depth < $seedDepth) {
                $depth = $seedDepth;
            }
        }
        $depth++;
        return $depth;
    }
    
    public function getDescendantsCount(){
        $DescendantsCount = 0;
        $seeds = $this->seed;
        echo $this->name() . '->';
        foreach ($seeds as $seed) {
            $DescendantsCount++;
            $DescendantsCount += $seed->getDescendantsCount();
        }
        return $DescendantsCount;
    }
    
    public function appendChild($child) {
        //даём ссылку на потомка
        $this->seed[] = $child;
        $child->appendParent($this);
    }
    //функция appendParent($child) является продолжение функции appendChild()
    //и не имеет смысла её использование вне  функции appendChild()
    //Не забыть спросить о том, как запретить её вызов другими функциями 
    //или пользователем
    //подобно тому как лычка protected запрещает обращатся к переменной
    //из другого обьекта

    public function appendParent($parent) {
        //даём ссылку на корень
        $this->root = $parent;
    }
    
    public function remove(){
        if (isset($this->root)) {
            $root = $this->root;
            $root->removeChild($this);
            unset($this->root);
        }
    }
    //функция removeChild($child) является продолжение функции remove()
    //и не имеет смысла её использование вне функции remove()
    //Не забыть спросить о том, как запретить её вызов другими функциями 
    //или пользователем.
    //подобно тому как лычка protected запрещает обращатся к переменной
    //из другого обьекта
    public function removeChild($child) {
        $seeds = $this->seed;
        foreach ($seeds as $seed) {
            if ($seed == $child) {
                unset($child);
                break;
            }
        }
    }
    
    public function getNextSibling(){
        $root = $this->root;
        if (NULL ==$root) {
            return NULL;
        }
        $siblings = $root->getSeed();
        foreach ($siblings as $key => $sibling) {
            if ($sibling == $this) {
                if (isset($siblings[$key+1])) {
                    return $siblings[$key+1];
                } else { 
                    return NULL;
                }
            }
        }
    }
    public function getPreviousSibling(){
        $root = $this->root;
        if (NULL ==$root) {
            return NULL;
        }
        $siblings = $root->getSeed();
        foreach ($siblings as $key => $sibling) {
            if ($sibling == $this) {
                if (isset($siblings[$key-1])) {
                    return $siblings[$key-1];
                } else { 
                    return NULL;
                }
            }
        }
    }
    public function getSeed () {
        return $this->seed;
    }
    
    /*
    
    public function walk(function ($child) {
    ...
    }); 
    // обходит всех потомков $node, для каждого вызывая функцию
        
        WAT ???
    */
    public function walk($function) {
        $children = $this->getAllChildren();
        foreach ($children as $child) {
            echo "\nОперацию над обьектом " . $child->getName() 
               . " совершает функция {$function}\n";
            echo $child->$function();
        }
    }
    
}
    
    
//создаём узлы 1 -> 2
//               -> 3 -> 4
//               -> 5
$node1 = new Node('node1');

$node2 = new Node('node2');
$node1->appendChild($node2);

$node3 = new Node('node3');
$node1->appendChild($node3);

$node4 = new Node('node4');
$node3->appendChild($node4);

$node5 = new Node('node5');
$node1->appendChild($node5);

/*$parents = $node4->getAllParents();
    foreach ($parents as $node) {
        echo $node->getName() . '->';
    }*/

/*$names = array();
$children = $node1->getChildrenNode();
foreach ($children as $child) {
    $names[] = $child->getName();
}
print_r ($names);*/
 
/* 
$children = $node3->getAllChildren();
    foreach ($children as $node) {
        echo $node->getName() . '->';
    }
  */  
    
//echo $node1->getDepth();

$function = "getDepth";
$node1->walk($function);

//echo "\n" . $node3->getDescendantsCount();