1<?php
2
3namespace ComboStrap;
4
5/**
6 * A class to modeling a tree
7 */
8abstract class TreeNode
9{
10
11    /**
12     * @var TreeNode[]
13     */
14    private array $children = [];
15
16    private TreeNode $parentNode;
17
18    /**
19     * @var int the child number when added
20     * used to get a unique identifier in the tree
21     */
22    private int $levelChildIdentifier;
23
24
25    /**
26     * @throws ExceptionBadState - if the node has already been added and is not the same than in the tree
27     */
28    public function appendChild(TreeNode $treeNode): TreeNode
29    {
30        try {
31            $levelChildIdentifier = $treeNode->getLevelChildIdentifier();
32        } catch (ExceptionNotFound $e) {
33            // no level child identifier
34            $childCount = count($this->children);
35            $levelChildIdentifier = $childCount + 1;
36            $treeNode->setLevelChildIdentifier($levelChildIdentifier);
37            $treeNode->setParent($this);
38            $this->children[$levelChildIdentifier] = $treeNode;
39            return $this;
40        }
41        $actualTreeNode = $this->children[$levelChildIdentifier];
42        if ($actualTreeNode !== $treeNode) {
43            throw new ExceptionBadState("The node ($treeNode) was already added but not on this level");
44        }
45        return $this;
46    }
47
48    public function getLocalId()
49    {
50
51    }
52
53
54    public
55    function __toString()
56    {
57        return $this->getTreeIdentifier();
58    }
59
60
61    public
62    function getChildCount(): int
63    {
64        return sizeof($this->children);
65    }
66
67    /**
68     * @return TreeNode[] - empty array for a leaf
69     */
70    public function getChildren(): array
71    {
72        return $this->children;
73    }
74
75
76    public function hasChildren(): bool
77    {
78        return sizeof($this->children) > 0;
79    }
80
81    public function hasParent(): bool
82    {
83        return isset($this->parentNode);
84    }
85
86    /**
87     * @return TreeNode
88     * @throws ExceptionNotFound
89     */
90    public function getFirstChild(): TreeNode
91    {
92        $childrenKeys = array_keys($this->children);
93        if (sizeof($childrenKeys) === 0) {
94            throw new ExceptionNotFound("This node has no child");
95        }
96        return $this->children[$childrenKeys[0]];
97    }
98
99    /**
100     * A hierarchical tree identifier composed
101     * of the node id of each level separated by a point
102     * @return string
103     */
104    function getTreeIdentifier(): string
105    {
106
107        $treeIdentifier = $this->getLevelChildIdentifier();
108        $parent = $this;
109        while (true) {
110            try {
111                $parent = $parent->getParent();
112                $parentLevelNodeIdentifier = $parent->getLevelChildIdentifier();
113                if ($parentLevelNodeIdentifier !== "") {
114                    $treeIdentifier = "{$parentLevelNodeIdentifier}.$treeIdentifier";
115                } else {
116                    $treeIdentifier = "$treeIdentifier";
117                }
118            } catch (ExceptionNotFound $e) {
119                // no parent anymore
120                break;
121            }
122
123        }
124        return $treeIdentifier;
125    }
126
127
128    private function setParent(TreeNode $parent)
129    {
130        $this->parentNode = $parent;
131    }
132
133    /**
134     * @throws ExceptionNotFound
135     */
136    public function getParent(): TreeNode
137    {
138        if (!isset($this->parentNode)) {
139            throw new ExceptionNotFound("No parent node");
140        }
141        return $this->parentNode;
142    }
143
144    private function setLevelChildIdentifier(int $levelIdentifier)
145    {
146        $this->levelChildIdentifier = $levelIdentifier;
147    }
148
149    public function detachBeforeAppend(){
150
151        unset($this->parentNode->children[$this->levelChildIdentifier]);
152        unset($this->levelChildIdentifier);
153        unset($this->parentNode);
154
155    }
156
157    /**
158     * @throws ExceptionNotFound
159     */
160    private function getLevelChildIdentifier(): int
161    {
162        if (!isset($this->levelChildIdentifier)) {
163            throw new ExceptionNotFound("No child identifier level found");
164        }
165        return $this->levelChildIdentifier;
166    }
167
168
169}
170