<?php

namespace Snaju\Inception\Route;

use Snaju\Inception\InceptionCore;
use Snaju\Inception\Route\Loaders\Twig\TemplateManager;
use Snaju\Inception\Util\Util;

class RouteNode
{

    /**
     * @var array
     */
    private array $children = [];

    /**
     * @var RouteNode
     */
    private $parent;

    /**
     * @var string
     */
    private $routePart;

    /**
     * @var Route
     */
    private $routeObject;

    /**
     * @var array
     */
    private array $data = [];

    private float $weight = 0;

    /**
     * RouteNode constructor.
     * @param $parent
     * @param $routePart
     * @param null $routeObject
     */
    public function __construct($parent, $routePart, $routeObject = null)
    {
        $this->parent = $parent;
        $this->routePart = $routePart;
        $this->routeObject = $routeObject;
    }

    /**
     * @return float|int
     */
    public function getWeight(): float|int
    {
        return $this->weight;
    }

    /**
     * @param float|int $weight
     */
    public function setWeight(float|int $weight): void
    {
        $this->weight = $weight;
    }

    /**
     * @return array
     */
    public function getChildren(): array
    {
        return $this->children;
    }

    /**
     * @param array $children
     */
    public function setChildren(array $children): void
    {
        $this->children = $children;
    }

    /**
     * @return mixed
     */
    public function getParent()
    {
        return $this->parent;
    }

    /**
     * @param mixed $parent
     */
    public function setParent($parent): void
    {
        $this->parent = $parent;
    }

    /**
     * @return mixed
     */
    public function getRoutePart()
    {
        return $this->routePart;
    }

    public function isWildCard(&$m)
    {
        return preg_match('/\{(.*?)\}/mi', $this->getRoutePart(), $m);
    }

    /**
     * @param mixed $routePart
     */
    public function setRoutePart($routePart): void
    {
        $this->routePart = $routePart;
    }

    public function addChild(RouteNode $node, bool $strict = false)
    {
        if (!$this->hasChild($node->getRoutePart(), $strict)) {
            $node->setParent($this);
            $this->children[] = $node;
        }

        return $this;
    }

    public function removeChild(string $stringPart)
    {
        foreach ($this->children as $i => $child) {
            if ($child instanceof RouteNode) {
                if (strtolower($child->routePart) == strtolower($stringPart)) {
                    $this->children[$i] = null;
                    unset($this->children[$i]);
                }
            }
        }
    }

    public function getPartWeights(string $part, &$weights = [])
    {
        /*
         * first check for a real value
         * */
        foreach ($this->children as $i => $child) {
            if ($child instanceof RouteNode) {

                $w = 0;

                if (strtolower($child->getRoutePart()) == strtolower($part)) {
                    $w = 10;
                } else if ($child->isWildCard($m)) {
                    $w = 5;
                }

                if (isset($weights[$child->getFullPath()])) {
                    $weights[$child->getFullPath()] += $w;
                } else {
                    $weights[$child->getFullPath()] = $w;
                }
            }
        }
    }

    public function getChild(string $part, bool $strict = false)
    {

        $weightedMatches = [];

        /*
         * first check for a real value
         * */
        foreach ($this->children as $i => $child) {
            if ($child instanceof RouteNode) {
                if (strtolower($child->getRoutePart()) == strtolower($part)) {
                    $weightedMatches[$i] = 10;
                } else if (!$strict && $child->isWildCard($m)) {
                    $weightedMatches[$i] = 5;
                }
            }
        }

        // Find the heighest weighted match
        arsort($weightedMatches);

        if (count($weightedMatches) > 0) {
            $highest = $this->children[array_keys($weightedMatches)[0]];
            if ($highest instanceof RouteNode) {
                if ($highest->isWildCard($m)) {
                    // Skip adding it to the data it is getting lost somewhere, just add it to the request.
                    InceptionCore::getRequest()->mergeAttributes([
                        $m[1] => $part
                    ]);

                    // Add to the URL data
                    TemplateManager::addData("url.{$m[1]}", $part);
                }

                return $highest;
            }
        }

        return null;
    }
    
    public function printOut()
    {
        echo "(Node: " . $this->routePart . " (<br/>";
        foreach ($this->children as $child) {
            if ($child instanceof RouteNode) {
                $child->printOut();
            }
        }
        echo ")<br/>";
    }

    public function getArray()
    {
        $ch = [];
        foreach ($this->children as $c) {
            if ($c instanceof RouteNode) {
                $ch[] = $c->getArray();
            }
        }

        return [
            "weight" => $this->weight,
            "part" => $this->getRoutePart(),
            "isRoute" => ($this->getRouteObject() != null),
            "children" => $ch
        ];
    }

    public function hasChildren()
    {
        return (count($this->children) > 0);
    }

    public function hasChild(string $part, bool $strict = false)
    {
        $n = $this->getChild($part, $strict);
        return ($n instanceof RouteNode);
    }

    public function getAllParents($parts = [])
    {
        $parts[] = $this->getRoutePart();
        $parent = $this->getParent();
        if ($parent instanceof RouteNode) {
            $parts = $parent->getAllParents($parts);
        }

        return $parts;
    }

    public function getAllParentsObjects($parents = [])
    {
        $parents[] = $this;
        $parent = $this->getParent();
        if ($parent instanceof RouteNode) {
            $parents = $parent->getAllParentsObjects($parents);
        }

        return $parents;
    }

    public function getFullPath()
    {
        $parents = $this->getAllParents();
        $parents = array_reverse($parents);

        $s = "";
        foreach ($parents as $parent) {
            $parent = str_replace("/", "", $parent);
            if (!empty($parent)) {
                $s .= "/$parent";
            }
        }

        if (empty($s)) {
//            $s = "/";
        }

        return $s;
    }

    /**
     * @return Route|null
     */
    public function &getRouteObject(): ?Route
    {
        return $this->routeObject;
    }

    /**
     * @param Route $routeObject
     */
    public function setRouteObject(Route $routeObject): void
    {
        $this->routeObject = $routeObject;
    }

    /**
     * @return array
     */
    public function getData(): array
    {
        return $this->data;
    }

    public function getRoutes(&$arr = []): array
    {
        if ($this->routeObject != null) {
            $arr[$this->getFullPath()] = $this;
        }

        foreach ($this->children as $child) {
            if ($child instanceof RouteNode) {
                $child->getRoutes($arr);
            }
        }

        return $arr;
    }

    public function allZeroWeights()
    {

//        if ($this->weight > 0) {
//            return false;
//        }

        foreach ($this->children as $c) {
            if ($c instanceof RouteNode) {
                if ($c->getWeight() > 0) {
                    return false;
                }
            }
        }

        return true;
    }

    public function getHighestChild()
    {
        $h = 0;
        $he = null;

        if (count($this->getChildren()) == 1) {
            $he = $this->getChildren()[0];
            return $he;
        }

        foreach ($this->children as $c) {
            if ($c instanceof RouteNode) {
                if ($c->getWeight() > $h) {
                    $h = $c->getWeight();
                    $he = $c;
                }
            }
        }

        return $he;
    }

    public function totalWeight()
    {
        foreach ($this->children as $child) {
            if ($child instanceof RouteNode) {
                $child->totalWeight();
                $this->weight += $child->getWeight();
            }
        }
    }

    public function calcWeight(string $searchPath): RouteNode
    {
        $w = 0;

        $p = explode("/", $searchPath);
        $p = Util::cleanArray($p);

        if (isset($p[0])) {
            $thisSearchFor = $p[0];
            $newPath = implode("/", array_slice($p, 1));

            if ($this->hasChild($thisSearchFor, true)) {
                // Strict True
                $this->getChild($thisSearchFor, true)->setWeight(10);
            } else if ($this->hasChild($thisSearchFor)) {
                $this->getChild($thisSearchFor)->setWeight(5);
            }

            foreach ($this->getChildren() as $c) {
                if ($c instanceof RouteNode) {
                    $c->calcWeight($newPath)->getWeight();
                }
            }
        }

//        $this->weight = $w;
        return $this;
    }

}

?>