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

    private $parentNode = NULL;
    private $child = array();
    protected $name;
    
    public function __construct($name) {
        $this->name = $name;
    }
    public function __toString() {
        $node = "\nNameNode: " . $this->name . "\n";
 /*       $node .= "ParentName: " . $this->parentNode->getName() . "\n";
        foreach ($this->child as $child) {
            $node .= "ChildName: " . $child->getName() ."\n";
        }
        */
        return $node;
    }
    public function getName() {
        return $this->name;
    }
    
    public function getParentNode(){ 
        return $this->parentNode ? $this->parentNode : NULL;
    }
    public function getChildrenNode() {
        return $this->child;
    }
    
    public function getAllParents(){
        if (!$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() {
        $children = $this->child;
        $depth = -1;
        //echo $this->name() . '->';
        foreach ($children as $child) {
            $depth = max($depth, $child->getDepth());
        }
        $depth++;
        return $depth;
    }
    
    public function getDescendantsCount(){
        $DescendantsCount = 0;
        $children = $this->child;
        echo $this->getName() . '->';
        foreach ($children as $child) {
            $DescendantsCount++;
            $DescendantsCount += $child->getDescendantsCount();
        }
        return $DescendantsCount;
    }
    
    public function appendChild(Node $child) {
        
        if ($this == $child) {
            throw new Exception("ОШИБКА! Нельзя делать обьект " . $this->getName() . " родителем самого себя.\n");
        }
        
        $parents = $this->getAllParents();
        if (in_array($child, $parents)) {
            throw new Exception("ОШИБКА! Нельзя делать обьект " . $this->getName() . " потомком самого себя.\n");
        }
        
        $child->remove();
        
        $this->child[] = $child;
        $child->parentNode = $this;
    }

    public function remove(){
        if ($this->parentNode) {
            echo "\nУдаление\n";
            $parentNode = $this->parentNode;
            
            $children = $parentNode->child;
            $key = array_search($this , $children);
            unset($parentNode->child[$key]);
            
            $this->parentNode = null;
        }
    }
    
    public function getNextSibling(){
        $parentNode = $this->parentNode;
        if (NULL ==$parentNode) {
            return NULL;
        }
        $siblings = $parentNode->getChildrenNode();

        while (current($siblings) <> $this) {
        next($siblings);
        }
        
        return next($siblings);
    }
    public function getPreviousSibling(){
        $parentNode = $this->parentNode;
        if (NULL ==$parentNode) {
            return NULL;
        }
        $siblings = $parentNode->getChildrenNode();
        
        //Переходим к элементу для которого требуется найти брата
        while (current($siblings) <> $this) {
        next($siblings);
        }
        
        return prev($siblings);
        
    }
    
    public function walk($function) {
    
        call_user_func($function, $this);
        $children = $this->getChildrenNode();
        foreach ($children as $child) {
            $child->walk($function);
        }
    }

}
    
class Creator {
  
    function createTree() {
        $arrayTree = array
        ( 
            'root' => array
            (
                'electronic' => array
                (   
                    'tv'     => array(),
                    'player' => array
                    ( 
                        'dvd' => array(), 
                        'mp3' => array() 
                    ),
                    'camera' => array()
                ),
                'appliance'  => array
                ( 
                    'refrigerator' => array(),  
                    'hoover'       => array(), 
                    'washer'       => array()
                ),
                'computer'   => array
                (
                    'desktop'  => array(),
                    'notebook' => array
                    ( 
                        'apple'  => array(), 
                        'others' => array() 
                    ),
                    'tablet'  => array(),
                    'printer' => array()
                ),
                'reference'  => array
                (
                    'about'    => array(),
                    'delivery' => array(),
                    'payment'  => array()
                )
            )
        );
   
    function getObjectTree($arrayTree, $parent)
    {
    //рекурсивная свистопляска

        foreach ($arrayTree as $name => $node) 
        {
            $$name = new Node($name);
                if ($parent) { $parent->appendChild($$name); }
            getObjectTree($node, $$name);
        }
        //если узел лишён родителя, то его нужно вывести
        if (!$parent) {return $$name;}
    }
    
    $parent = NULL;
    $root = getObjectTree($arrayTree, $parent);

    return $root;
 
    }
}

class View {
    const COLUMN_SPACING = 4;
    
    function getColumn($node, $width) {
        $level = count($node->getAllParents());
        
        if ($level == 1) {
            $word = "  " . $node->getName();
            $column[] = str_pad($word, $width);
            $column[] = str_pad('', $width);
        }
        if ($level == 2) {
            $word = " -" . $node->getName();
            $column[] = str_pad($word, $width);
        }
        if ($level > 2) {
            $indent = ($level-1) * 2;
            $word = str_pad(' ', $indent ) . $node->getName();
            $column[] = str_pad($word, $width);
        }
        
        $children = $node->getChildrenNode();
        foreach ($children as $child) {
            $getColumn = $this->getColumn($child, $width);
            $column = array_merge($column,$getColumn);
        }
        
        return $column;
        
    }

    function getWidthColumn($root) {
        //определение ширины колонки
        $rootChildren = $root->getChildrenNode();
        $widthColumn = array();
        
        foreach ($rootChildren as $rootChild) {
            //ширина первого пункта меню
            $width = mb_strlen($rootChild->getName()) + 2 ;
            $allChildrenNode = $rootChild->getAllChildren();

            foreach ($allChildrenNode as $node) {
                $level = count($node->getAllParents());
                
                    $thisWidthNode = mb_strlen($node->getName()) + (2 * ($level - 1));
                $width = max($width,$thisWidthNode);
            }
            //к ширине строки прибавляется расстояние между колонками 4
            $widthColumn[] = $width+self::COLUMN_SPACING;
        }
            return $widthColumn;
    }
    
    function getHeightColumn($root){
        $heightColumn = 0;
        $rootChildren = $root->getChildrenNode();

     
        foreach ($rootChildren as $rootChild) {
            
            //находим пункты меню(кроме первых уровней) данного столбца.
            $menuItems = $rootChild->getAllChildren();
            //получаем колличество строк в меню
            $heightThisColumn = count($menuItems) + 2;
            $heightColumn = max($heightColumn, $heightThisColumn);
        }
       
        return $heightColumn;
    }
    
    function getTextMenu($root) {
        $widthColumn = $this->getWidthColumn($root);
        $heightColumn = $this->getHeightColumn($root);
        

        $children = $root->getChildrenNode();
        $countColumn = count($children);
        $menu = '';
        
        $column = array();
        $i = -1;
        foreach ($children as $child) { 
            $i++;
            $columns[] = $this->getColumn($child, $widthColumn[$i]);
        }

        for ( $i = 0 ; $i < $heightColumn ; $i++ ) {
            for ( $j = 0 ; $j < $countColumn ; $j++ ) {
                if (isset($columns[$j][$i])) {
                    $menu.= $columns[$j][$i];
                }else{
                    $menu.= str_pad('', $widthColumn[$j]);
                }
            }
            $menu .= "\n";
        }
        
        return $menu;
    }
    
    function writeMenu(){
        $creator = new Creator();
        $root = $creator->createTree();
        $menu = $this->getTextMenu($root);
        return $menu;
    }
}

//$view = new View;
//echo $view->writeMenu() . "\n";


//тут тестирую функцию walk
echo "приступить к тесту \n\n\n";
function myFunction($node) {
    echo $node->getName() . "\n";
}


$creator = new Creator;
$root = $creator->createTree();

$root->walk('myFunction');




