<?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(){ 
        if (isset($this->parentNode)) {
            return $this->parentNode;
        }
        return NULL;
    }
    public function getChildrenNode() {
        return $this->child;
    }
    
    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() {
        $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 (isset($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) {
        $allChildren = $this->getAllChildren();
        $return = array();
        $callback = 
            function ( $child ) use ( $function, &$return) {
                $return[] = "Имя узла: " . $child->getName() . "    Количество потомков: "
                . $child->$function();
            };
        array_walk( $allChildren, $callback );
        print_r($return);
    }
    // обходит всех потомков $node, для каждого вызывая функцию
        

    
}
    
class Creator {
    function createTree() {
    $root = new Node('root');
        $electronic = new Node('electronic');
        $root->appendChild($electronic);
            $tv = new Node('tv');
            $electronic->appendChild($tv);
            $player = new Node('player');
            $electronic->appendChild($player);
                $dvd = new Node('dvd');
                $player->appendChild($dvd);
                $mp3 = new Node('mp3');
                $player->appendChild($mp3);
                
            $camera = new Node('camera');
            $electronic->appendChild($camera);
            
        $appliance = new Node('appliance');
        $root->appendChild($appliance);
            $refrigerator = new Node('refrigerator');
            $appliance->appendChild($refrigerator);
            $hoover = new Node('hoover');
            $appliance->appendChild($hoover);
            $washer = new Node('washer');
            $appliance->appendChild($washer);
            
        $computer = new Node('computer');
        $root->appendChild($computer);
            $desktop = new Node('desktop');
            $computer->appendChild($desktop);
            $notebook = new Node('notebook');
            $computer->appendChild($notebook);
                $apple = new Node('apple');
                $notebook->appendChild($apple);
                $others = new Node('others');
                $notebook->appendChild($others);
                
            $tablet = new Node('tablet');
            $computer->appendChild($tablet);
            $printer = new Node('printer');
            $computer->appendChild($printer);
            
        $reference = new Node('reference');
        $root->appendChild($reference);
            $about = new Node('about');
            $reference->appendChild($about);
            $delivery = new Node('delivery');
            $reference->appendChild($delivery);
            $payment = new Node('payment');
            $reference->appendChild($payment);
    return $root;
    }
}

class View {

    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+4;
        }
            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();



