<?php

// start session
session_start();
// if session just started and uid is not set, add initial information such as user agent and ip address


if (!isset($_SESSION['uid']))
{
    $_SESSION['ipremote'] = $_SERVER['REMOTE_ADDR'];

    $_SESSION['useragent'] = $_SERVER['HTTP_USER_AGENT'];
}
else
{
    // uid is set in session variables. User gets logged out in case of changed ip address and user agent.
    if ($_SESSION['ipremote'] !== $_SERVER['REMOTE_ADDR'] && $_SESSION['useragent'] !== $_SERVER['HTTP_USER_AGENT'])
    {
        session_unset();
        session_destroy();
    }
    // if client information has not changed just regenerate the session
    session_regenerate_id(true);
}

const VERBOSITY = 0;
const PUBLIC_DIRNAME = "public";
const CONFIG_WEBROUTES = "/../routes/web.php"; // like in laravel
const CONFIG_DB = "/../config/db.php";
const ROUTER_VERSION = '0.8.2';

assert_php_version('8.2.0');
assert_path();

try {
    if (!file_exists(realpath($_SERVER['DOCUMENT_ROOT'] . "/../vendor/autoload.php"))) {
        echo "<h1>Abhängigkeiten nicht gefunden</h1><pre>DOCUMENT_ROOT: {$_SERVER['DOCUMENT_ROOT']}</pre><br><p>Datei nicht gefunden: <strong>{$_SERVER['DOCUMENT_ROOT']}/../vendor/autoload.php</strong></p>";
        echo "<p>Häufigste Ursache</p><ul>
            <li>Das Verzeichnis <code>public/</code> ist <em>nicht</em> als Wurzelverzeichnis verwendet worden.</li>
            <li>Die Abhängigkeiten wurden nicht mit <code>composer update</code> installiert.</code></li>
            </ul>";
        exit(1);
    }
    // file exists
    require_once realpath($_SERVER['DOCUMENT_ROOT'] . "/../vendor/autoload.php");

} catch (Exception $ex) {
    echo "<code>DOCUMENT_ROOT</code><br><pre>{$_SERVER['DOCUMENT_ROOT']}</pre><code>Error</code><br><pre>" . $ex->getMessage() . "</pre>";
}

use eftec\bladeone\BladeOne;

/* Routing Script for PHP Dev Server */
$verbosity = VERBOSITY;
if (preg_match('/\.(?:css|js|png|jpg|jpeg|gif)$/', $_SERVER["REQUEST_URI"])) {
    return false;
} else {
    if ($verbosity > 1) {
        echo
            "<pre>Verbosity-Level: <strong>{$verbosity}</strong></pre>" .
            "<pre>" . print_r($_SERVER, 1) . "</pre><hr>";
    }
    FrontController::handleRequest("$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]", $_SERVER['REQUEST_METHOD'], VERBOSITY);
}

class RequestData
{
    /**
     * @var array Request Querystring, broken down to key-value pairs
     */
    public array $query;
    /**
     * @var array Request arguments from path, after cutting two segments out for controller and action names
     */
    public array $args;
    /**
     * @var string HTTP Verb used
     */
    public string $method;

    public function getData()
    {
        return array_merge($_GET, $_POST);
    }

    /**
     * @return array
     */
    public function getPostData()
    {
        return $_POST;
    }

    /**
     * @return array
     */
    public function getGetData()
    {
        return $_GET;
    }

    /**
     * RequestData is the way the Router will provide information, use it in your Action methods.
     * @param $method string Verb used
     * @param $args array Arguments
     * @param $query array Key-Value Pairs
     */
    public function __construct($method, $args, $query)
    {
        $this->query = $query;
        $this->args = $args;
        $this->method = $method;
    }
}

class FrontController
{

    public static function handleRequest($url, $method = 'GET', $verbosity = 0, $configPath = CONFIG_WEBROUTES)
    {
        assert_blade(); // check if the class is found

        if (!str_contains($url, ':')) $url = $_SERVER['HTTP_HOST'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI'];

        $scriptPath = dirname(__FILE__, 2) . '/';
        $controllerDirectory = $scriptPath . 'controllers/';

        // get a router/web-like config array to override filebased convention
        $config = FrontController::getConfig($configPath);

        // /Impressum/ --> ImpressumController->index()
        $request = parse_url($url);
        $ctrlName = $request['path'];
        $actionName = 'index';
        $args = array();
        $query = array();
        parse_str($request['query'] ?? "", $query);

        // provide POST data
        if ($method != 'GET')
            $query = array_merge($query, $_REQUEST);


        // check, if route has two levels
        if (strpos($ctrlName, '/', 1) > 0) {
            $path = explode('/', $request['path']); // Werbeseite/Speise/1/mobile?pretty=true&user=marcel
            array_shift($path);                             // skip once
            $ctrlName = array_shift($path);         // Werbeseite
            $actionName = array_shift($path);       // Speise
            $args = $path; // remainder of path-parts     // [1][mobile]
            if ($verbosity > 1) {
                echo "<pre>Request\n", print_r($request), "</pre>";
                echo "<pre>Path\n", print_r($path), "</pre>";
                echo "<pre>Query\n", print_r($query), "</pre>";
            }
        }

        // fix: trim slashes
        $ctrlName = trim($ctrlName, '/');
        $actionName = trim($actionName, '/');

        // $config based renaming of Controller/Action, precedes filebased convention
        // $config values must use syntax <ClassController>@<actionname>
        if (array_key_exists('/' . $ctrlName . '/' . $actionName, $config)) {
            $routingConfig = explode('@', $config['/' . $ctrlName . '/' . $actionName]);
            if ($verbosity > 0) {
                echo "<p>Routing Config matched request for <code>/" . $ctrlName . "/" . $actionName . "</code>:</p><p>routing config is</p><pre>" . print_r($routingConfig, 1) . '</pre>';
            }
            // important: overwriting controller and action name
            $ctrlClass = $routingConfig[0];
            $actionName = $routingConfig[1];
        } elseif (array_key_exists($request['path'], $config)) {
            // exact match on full path, this also means "/"
            $routingConfig = explode('@', $config[$request['path']]);
            if ($verbosity > 0) {
                echo "<p>Routing Config matched for full path <code>" . $request['path'] . "</code>:</p><p>routing config is</p><pre>" . print_r($routingConfig, 1) . '</pre>';
            }
            // important: overwriting controller and action name
            $ctrlClass = $routingConfig[0];
            $actionName = $routingConfig[1];
        } else {
            if ($verbosity > 0) {
                echo "Request $ctrlName/$actionName was not in \$config.";
            }

            // fall back to filebased convention: match controller classes in directory
            $ctrlClass = ucfirst($ctrlName . 'Controller');
        }

        $ctrlFile = ($ctrlClass . '.php');
        $validControllers = FrontController::getValidControllers($controllerDirectory);
        if (!in_array($controllerDirectory . $ctrlFile, $validControllers)) {
            if ($verbosity > 0) {
                echo "<div><p>Controller: $ctrlFile not found in</p><pre>" . print_r($validControllers, 1) . "</pre><p>Config Array:</p><pre>" . print_r($config, 1) . "</pre></div>";
            }
            // #ERROR
            FrontController::showErrorMessage("<h1>Web Software Error</h1><img alt='shrug' src='https://c.tenor.com/9TEDud6eP2UAAAAC/woody-woodpecker-shrug-shoulders.gif'>" .
                "<p>Keine entspreche Zuordnung der Route für {$ctrlName}::{$actionName} gefunden. Tippfehler in der Route?" .
                "<p>Es konnte keine Klasse <abbr title='Gesucht im Verzeichnis {$controllerDirectory}'>" . $ctrlFile . "</abbr> gefunden werden! Request fehlgeschlagen.</p>" .
                "<p> Prüfen Sie die Einträge in der Datei <code>config/web.php</code> und gleichen Sie den getätigten Aufruf damit ab.</p>");
        }

        // a file matching has been found, now try to load the class
        try {
            require_once $controllerDirectory . $ctrlFile;
            // instantiate the controller

            $controller = new $ctrlClass();
            $rd = new RequestData($method, $args, $query);

            if ($verbosity > 0) {
                var_dump($controller, $rd);
            }
            // the controller will load model and view and return some html
            print call_user_func_array(array($controller, $actionName), array($rd));
        } catch (Exception $ex) {
            // #ERROR
            FrontController::showErrorMessage(
                "<h2>Fehler in Controller " . get_class($controller) . "!</h2><p>Stellen Sie sicher, dass die Action/der Controller existiert.</p>
                    <p>Das Routing Config-Array hat " . count($config) . " Einträge.</p>
                    <p><strong>Exception text</strong><br>" . $ex->getMessage() . "</p>");
        }
    }

    public static function showErrorMessage($text, $severity = 3, $die = true)
    {
        $styles = array(0 => "background-color: #F08170; border: 2px solid lightgray; padding: 2em; margin: 5em; width: 50%; box-shadow: 0em 0em 1em #F08170;",
            1 => "background-color: #F08170; border: 2px solid lightgray; padding: 2em; margin: 5em; width: 50%; box-shadow: 0em 0em 1em #F08170;",
            2 => "background-color: #F08170; border: 2px solid lightgray; padding: 2em; margin: 5em; width: 50%; box-shadow: 0em 0em 1em #F08170;",
            3 => "background-color: #F08170; border: 2px solid lightgray; padding: 2em; margin: 5em; width: 50%; box-shadow: 0em 0em 1em #F08170;");
        print("<div style=\"{$styles[$severity]}\">{$text}</div>");
        if ($die) exit($severity);
    }

    public static function getConfig($configPath)
    {
        try {
            // load the $config Array from a file given in $configPath
            $path_to_config = realpath($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $configPath);
            // print("Path to config " . $path_to_config);
            $config = include $path_to_config;
        } catch (Exception $e) {
            print_r($e);
            $config = array('/' => 'HomeController@index');
        } finally {
            return $config;
        }
    }

    public static function getValidControllers($path = '')
    {
        if ($path == '') {
            $path = getcwd() . DIRECTORY_SEPARATOR . 'controllers' . DIRECTORY_SEPARATOR;
        }
        return glob($path . '*Controller.php');
    }
}

function connectdb()
{
    $path_to_config_db = $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . CONFIG_DB;
    $config = include $path_to_config_db;
    $link = mysqli_connect(     // Daten aus config/db.php
        $config['host'],
        $config['user'],
        $config['password'],
        $config['database'],    // Auswahl der Datenbank
        $config['port'] ?? 3306);
    if (!$link) {
        FrontController::showErrorMessage("<h1>Verbindung mit der Datenbank nicht möglich</h1>
            <p style='margin-bottom: 2em;'>Verbindung fehlgeschlagen: <code>" . mysqli_connect_error() . "</code>.</p>
            <h2>Prüfen Sie</h2><ol><li>die Angaben in der Datei <code>config/db.php</code>:
            ( ist Benutzer <code>{$config['user']}</code> an Datenbank <code>{$config['database']}</code> auf Server <code>{$config['host']}</code> korrekt?)<br>
            </li><li>ob Ihre Datenbank unter der oben gezeigten Adresse läuft </li></ol>");
        exit(1);
    }
    if (mysqli_connect_errno()) {
        FrontController::showErrorMessage("<h1>Verbindung mit der Datenbank nicht möglich</h1>
            <code>Fehlermeldung</code><pre>" . mysqli_connect_error() . "</pre>", 2, true);
        exit(1);
    }

    return $link;
}

function view($viewname, $viewargs = array())
{
    $views = dirname(__DIR__) . '/views';
    $cache = dirname(__DIR__) . '/storage/cache';
    $blade = new BladeOne($views, $cache, BladeOne::MODE_DEBUG);

    return $blade->run($viewname, $viewargs);
}

/**
 * let the script die if the php minimum version is not met.
 * @param $minversion
 * @return void
 */
function assert_php_version($minversion = '8.0.0')
{
    $version_too_low = 0;
    $minver = explode('.', $minversion);
    $version = explode('.', phpversion());

    if (intval($minver[0]) > intval($version[0])) {
        $version_too_low = 1;
    } elseif (intval($minver[1]) > intval($version[1])) {
        $version_too_low = 1;
    } elseif (intval($minver[2]) > intval($version[2])) {
        $version_too_low = 1;
    }

    if ($version_too_low) {
        FrontController::showErrorMessage("Diese PHP-Version wird nicht unterstützt: <strong>Minimum PHP Version " . $minversion . "</strong><br>Sie betreiben gerade PHP Version " . phpversion());
        exit(1);
    }
    // version is okay, go on.
}

/**
 * let the script die if the path contains problematic characters.
 * @return void
 */
function assert_path(): void
{
    static $chars = array("[", "]", "{", "}");
    $charsfound = 0;
    str_ireplace($chars, '', $_SERVER['DOCUMENT_ROOT'], $charsfound);
    if ($charsfound > 0) {
        FrontController::showErrorMessage("<h1>Bitte verwenden Sie einen anderen Ordner für das Projekt!</h1>
        <p>Der Pfad <strong>" . $_SERVER['DOCUMENT_ROOT'] . "</strong> enthält <code>" . $charsfound . "</code> problematische Zeichen, die die korrekte Ausführung verhindern.</p>>
        <p>Bekannte problematische Zeichen sind</p>
        <pre> " . implode(" ", $chars) . " </pre>");
        exit(1);
    }

}

function assert_blade(): void
{
    if (!class_exists('eftec\bladeone\BladeOne')) {
        // #ERROR
        FrontController::showErrorMessage("
                <h1>Fehler: Blade wurde nicht gefunden</h1>
                <p>Tipps für die Lösung:</p>
                <ul>
                <li><p>führen Sie im Terminal folgende Zeilen aus.</p>
                <ol>
                    <li><code>php bin/composer.phar update</code> oder <code>php bin/composer.phar reinstall eftec/bladeone</code></li>
                    <li><code>php bin/composer.phar dump-autoload</code></li>
                </ol>
                <li><p>Prüfen Sie im Anschluss: befindet sich in dem Ordner <code>vendor/eftec/bladeone/lib/</code> die Datei <code>BladeOne.php</code> ?</p></li>
                <li><p>Starten Sie dann den Webserver neu.</p></li>
                <li><p>Befragen Sie gerne auch das Forum in Ilias.</p></li>
                </ul>
                
                </div>");
        exit(1);
    }
}

use Monolog\Level;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

function logger($name, $path){

    $fullpath = $path . '\\' . $name . '.txt';
    $log = new Logger($name);
    $log->pushHandler(new StreamHandler($fullpath), Level::Warning);
    return $log;
}
