<?php /** @noinspection PhpUnused */

namespace Grav\Plugin;

use Exception;
use Grav\Common\Grav;
use Grav\Common\Page\Page;
use Grav\Common\Page\Pages;
use Grav\Common\Plugin;
use Grav\Common\Uri;
use Symfony\Component\EventDispatcher\Event;


/**
 * Features:
 * - /sync   Cheap git-sync (did not knew about it at the time)
 * - /polyglot/<page-path>
 * - /lessons/<lesson-slug>
 *
 * The way GRAV works to define new routes is to listen to the onPageNotFound event.
 * This is expected to evolve as GRAV integrates more and more with Symfony components.
 *
 * Class M3P2Plugin
 * @package Grav\Plugin
 */
class M3P2Plugin extends Plugin
{
    /**
     * @return array
     *
     * The getSubscribedEvents() gives the core a list of events
     *     that the plugin wants to listen to. The key of each
     *     array section is the event that the plugin listens to
     *     and the value (in the form of an array) contains the
     *     callable (or function) as well as the priority. The
     *     higher the number the higher the priority.
     */
    public static function getSubscribedEvents()
    {
        return [
            'onPluginsInitialized' => ['onPluginsInitialized', 0]
        ];
    }

    /**
     * Initialize the plugin
     */
    public function onPluginsInitialized()
    {
        if ($this->isAdmin()) {
            return;  // Don't proceed if we are in the admin backend
        }

        $uri = $this->grav['uri'];
        $current_route = $uri->path();

        // /sync
        $route_sync = $this->config->get('plugins.m3-p2.routes.sync');
        if ($route_sync && $route_sync == $current_route) {
            $this->enable([
                'onPageInitialized' => ['onSyncPageInitialized', 0]
            ]);
        }

        $this->enable([
            'onTwigInitialized' => ['setupTwigGoodies', 0],
        ]);

        $this->enable([
            'onTwigInitialized' => ['setupWebIdeUrl', 0],
        ]);


//        $that_page_route = $uri->query('page');
//        if ($that_page_route) {
//            $this->enable([
//                'onPageNotFound' => ['setupQueryPage', 0],
//            ]);
//        }

        $priority = 1; // must be higher than the error plugin's

        $this->enable([
            'onPageNotFound' => ['perhapsDisplayPolyglotColumns', $priority],
        ]);

        $this->enable([
            'onPageNotFound' => ['perhapsDisplayLessonColumns', $priority],
        ]);

//        if ("" == $current_route) {
//            $this->enable([
//                'onPageInitialized' => ['onSyncPageInitialized', 0]
//            ]);
//        }

    }

    /**
     * Sync with the sources.
     */
    public function onSyncPageInitialized()
    {
        // shell cwd is project root
        $out = "";

        //$out = "Opening a fortune cookie…\n\n\n";
        //$out .= shell_exec("fortune") . "\n\n";
        //$out .= shell_exec("fortune | cowsay -e Oø -f dragon") . "\n";

        $out .= "Trying to sync content with GitLab…\n\n\n";
        $out .= shell_exec("cd user/pages && git pull --rebase --autostash origin master 2>&1") . "\n";

        $out .= "Done.";

        die("<pre>".$out."</pre>");  // This is all I could find to write to the page.
    }

    public function setupTwigGoodies()
    {
        /** @var Pages $pages */
        $pages = Grav::instance()['pages'];
        $this->grav['twig']->twig()->addGlobal('localized_pages', $pages);

        $this->grav['twig']->twig()->addFilter(
            new \Twig_SimpleFilter('to_page', [$this, 'getPageForPath'])
        );
    }

    public function setupWebIdeUrl()
    {
        $this->grav['twig']->twig()->addFilter(
            new \Twig_SimpleFilter('webide_url', [$this, 'getWebIdeUrlForPage'])
        );
        $this->grav['twig']->twig()->addFilter(
            new \Twig_SimpleFilter('translations', [$this, 'getTranslationsOfPage'])
        );
    }

//    public function setupQueryPage()
//    {
//        $that_page_route = $this->grav['uri']->query('page');
////        die($that_page_route);
//        var_dump($that_page_route);
//        $that_page = $this->grav['pages']->find($that_page_route);
//        var_dump($that_page);
//        $this->grav['twig']->twig()->addGlobal(
//            'that_page', $that_page
//        );
//    }

    public function getPageForPath($meta)
    {
        if (is_array($meta)) {
            if (isset($meta['path'])) {
                $path = $meta['path'];
            } else {
                trigger_error("Missing `path` in: ".print_r($meta, true));
            }
        } elseif (is_string($meta)) {
            $path = $meta;
        } else {
            trigger_error("Unrecognized page: ".print_r($meta, true));
        }

        /** @var Pages $pages */
        $pages = Grav::instance()['pages'];

        $page = $pages->find($path);

        if ( ! $page) {
            try {
                $page = $pages->get($path);
            } catch (Exception $e) {}
        }
        if ( ! $page) {
            try {
                $cwd = $pages->root()->filePath();  // ends with /
                if (0 === strpos($path, '/')) {
                    $page = $pages->get($cwd . substr($path, 1));
                } else {
                    $page = $pages->get($cwd . $path);
                }
            } catch (Exception $e) {}
        }

        return $page;
    }

    public function getWebIdeUrlForPage(Page $page)
    {
        $url = "https://gitlab.m3p2.com/-/ide/project/m3p2/courses/edit/master/-/";  # todo: move to YAML config
        $url .= substr($page->filePathClean(), 11); // 11==strlen('/user/pages')

        return $url;
    }

//    public function getPage($route)
//    {
//        $page = null;
////        $translation_path = $page->path() . DS . $page_name_without_ext . '.' . $language . '.md';
//        if (file_exists($translation_path)) {
//            $page = new Page();
//            $page->init(new \SplFileInfo($page_path), $language . '.md');
//        }
//
//        return $page;
//    }

    public function getTranslationsOfPage(Page $page)
    {
        $languages = $this->config->get('system.languages.supported');
//        die(print_r($languages));
        $route_suffix = $page->rawRoute();
        $translations = array();
        $current_language = $page->language();

        $page_name_without_ext = substr($page->name(), 0, -(strlen($page->extension())));
        foreach ($languages as $language) {
//            $route = '/' . $language . $route_suffix;
//            $route = $route_suffix;

            // Nope. language-selector does this the hard way, by instantiating Page objects
            //$translation = $page->find($route);

            // … so we're falling back on the old school too
            $translation_path = $page->path() . DS . $page_name_without_ext . '.' . $language . '.md';
            if ( ! file_exists($translation_path)) {
                continue;
            }
            $translation = new Page();
            $translation->init(new \SplFileInfo($translation_path), $language . '.md');

            if ($language == $current_language) {
                array_unshift($translations, $translation);
            } else {
                array_push($translations, $translation);
            }
        }

        return $translations;
    }

    public function perhapsDisplayPolyglotColumns(Event $event)
    {
        /** @var Uri $uri */
        $uri = $this->grav['uri'];
//        var_dump($uri); // best doc ftw

        if ("polyglot" != $uri->paths(0)) {
            return; // perhaps not
        }

        /** @var Pages $pages */
        $pages = $this->grav['pages'];

        $that_page_route = "/" . join("/", array_slice($uri->paths(), 1));

        $target_page = $pages->find($that_page_route, true);

        if ( ! $target_page) {
            return;
        }

        $this->grav['twig']->twig()->addGlobal('target_page', $target_page);
        $this->grav['twig']->twig()->addGlobal('disable_multilingual', true);

        $page = new Page;
        $page->init(new \SplFileInfo(__DIR__ . '/pages/polyglot-columns.md'));

        // here: perhaps add a title and other metadata

        $event->page = $page;
        $event->stopPropagation();
    }

    public function perhapsDisplayLessonColumns(Event $event)
    {
        /** @var Uri $uri */
        $uri = $this->grav['uri'];
//        var_dump($uri); // best doc ftw

        // GRAV's routing makes us hook 404 pages and do the URL match ourselves
        if ("lessons" != $uri->paths(0)) {
            return; // perhaps not
        }

        if (count($uri->paths()) < 2) {
            // user is toying with URI, probably, visiting `/lessons`.
            // idea: display an index of the lessons here todo
            return;
        }

        $lesson_slug = $uri->paths(1);

        /** @var Pages $pages */
        $pages = $this->grav['pages'];

        $target_pages = array_filter($pages->instances(), function (Page $p) use ($lesson_slug) {


            $page_config = $p->header();
            if ( ! isset($page_config->lessons) || empty($page_config->lessons)) {
                return false;
            }


            foreach ($page_config->lessons as $lesson) {

                if ( ! isset($lesson['slug'])) {
                    trigger_error(
                        "Misconfigured lesson (missing a slug) in ".($p->filePathClean()),
                        E_USER_WARNING
                    );
                    continue;
                }

                if ($lesson_slug == $lesson['slug']) {
                    return true; // yay, this page is part of the lesson
                }
            }

            return false;
        });

        usort($target_pages, function (Page $a, Page $b) use ($lesson_slug) {
            $la = 0;
            $lb = 0;

            $ha = $a->header();
            $hb = $b->header();

            $fdp = function ($lesson) use ($lesson_slug) {
                return trim($lesson["slug"]) === $lesson_slug;
            };

            $mar = array_filter($ha->lessons, $fdp);
            assert($mar && ! empty($mar), "No lesson found for $lesson_slug in page ".$a->url());
            $ma = array_shift($mar);

            $mbr = array_filter($hb->lessons, $fdp);
            assert($mbr && ! empty($mbr), "No lesson found for $lesson_slug in page ".$b->url());
            $mb = array_shift($mbr);

            if ($ma && isset($ma['order'])) {
                $la = floor($ma['order']);
            }
            if ($mb && isset($mb['order'])) {
                $lb = floor($mb['order']);
            }

            return $la - $lb;
        });


        if (empty($target_pages)) {
            return false;
        }

        $current_lesson_lang = $target_pages[0]->language();
        $current_lesson_name = "";  // unused for now
        $current_found = false;
        $previous_found = false;
        $next_found = false;
        $previous_lesson_slug = "";
        $previous_lesson_name = "";  // unused for now
        $next_lesson_slug = "";
        $other_lessons = [];  // of slug => ['name' => …, 'slug' => …]
        foreach ($target_pages as $target_page) {
            $page_config = $target_page->header();
            if ( ! isset($page_config->lessons) || empty($page_config->lessons)) {
                // Unless we edit the code above, this _should_ never happen.
                die("Some pages are lacking a lessons configuration.");
            }
            foreach ($page_config->lessons as $page_lesson) {
                if ( ! isset($page_lesson['slug'])) {
                    continue;
                }
                if ($lesson_slug != $page_lesson['slug']) {
                    if ( ! isset($other_lessons[$page_lesson['slug']])) {
                        // Found one, let's add it
                        $other_lessons[$page_lesson['slug']] = $page_lesson;

                        // And the prev/next navigation (the old way)
//                        if ($current_found) {
//                            if ( ! $next_found) {
//                                $next_lesson_slug = $page_lesson['slug'];
//                                $next_found = true;
//                            }
//                        } else {
//                            if ( ! $previous_found) {
//                                $previous_lesson_slug = $page_lesson['slug'];
//                                $previous_found = true;
//                            }
//                        }

                    }
                    continue;
                }

                $current_found = true;

                if (isset($page_lesson['name']) && ! empty($page_lesson['name'])) {
                    $current_lesson_name = $page_lesson['name'];
                }
            }
        }

        $redirects = array_values($this->getPagesWithRedirects());
        $amountOfRedirects = count($redirects);
        $redirect_parent_index = -1;
        foreach ($redirects as $k => $redirect) {
            if ($this->endsWith($redirect->redirect(), "/lessons/" . $lesson_slug)) {
                $redirect_parent_index = $k;
                break;
            }
        }
        if ($redirect_parent_index > -1) {
            if ($redirect_parent_index > 0) {
                $previous_lesson_slug = $this->extractLessonSlug($redirects[$redirect_parent_index - 1]->redirect());
            }
            if ($redirect_parent_index < $amountOfRedirects - 1) {
                $next_lesson_slug = $this->extractLessonSlug($redirects[$redirect_parent_index + 1]->redirect());
            }
        }

        $translated_lessons = [];  // lang => slug
        $translated_lessons_contenders = [];  // lang => [ slug => likelihood ]

        foreach ($target_pages as $target_page) {

            $translated_target_pages = $this->getTranslationsOfPage($target_page);
            foreach ($translated_target_pages as $translated_target_page) {
                /** @var Page $translated_target_page */
                $lang = $translated_target_page->language();
                if ($lang == $current_lesson_lang) { continue; }

                $page_config = $translated_target_page->header();
                if ( ! isset($page_config->lessons) || empty($page_config->lessons)) {
                    continue;  // other languages may not define lessons
                }

                foreach ($page_config->lessons as $lesson) {
                    if ( ! isset($lesson['slug'])) { continue; }

                    if ( ! isset($translated_lessons_contenders[$lang])) {
                        $translated_lessons_contenders[$lang] = [];
                    }

                    if ( ! isset($translated_lessons_contenders[$lang][$lesson['slug']])) {
                        $translated_lessons_contenders[$lang][$lesson['slug']] = 0;
                    }

                    $translated_lessons_contenders[$lang][$lesson['slug']] += 1;
                }
            }
        }

        foreach ($translated_lessons_contenders as $lang => $slug_to_likelihood) {
            $best_likelihood = 0;
            foreach ($slug_to_likelihood as $slug => $likelihood) {
                if ($likelihood > $best_likelihood) {
                    $best_likelihood = $likelihood;
                    $translated_lessons[$lang] = $slug;
                }
            }
        }

//        dd($translated_lessons);

        $this->grav['twig']->twig()->addGlobal('target_pages', $target_pages);
        $this->grav['twig']->twig()->addGlobal('related_lessons', $other_lessons);
        $this->grav['twig']->twig()->addGlobal('previous_lesson_slug', $previous_lesson_slug);
        $this->grav['twig']->twig()->addGlobal('next_lesson_slug', $next_lesson_slug);
        $this->grav['twig']->twig()->addGlobal('translated_lessons', $translated_lessons);

        $page = new Page();
        $page->init(new \SplFileInfo(__DIR__ . '/pages/lesson-columns.md'));
        $page->title($current_lesson_name);
        // here: perhaps add other metadata

        $event->page = $page;
        $event->stopPropagation();
    }

    public function getPagesWithRedirects()
    {
        /** @var Pages $pages */
        $pages = $this->grav['pages'];
        $target_pages = array_filter($pages->instances(), function (Page $p) {

            $page_config = $p->header();
            if (empty($p->redirect())) {
                return false;
            }

            return true;
        });

        return $target_pages;
    }


    protected function mbEndsWith($haystack, $needle, $case=true): bool
    {
        $expectedPosition = mb_strlen($haystack) - mb_strlen($needle);

        if ($case) {
            return mb_strrpos($haystack, $needle, 0) === $expectedPosition;
        }

        return mb_strripos($haystack, $needle, 0) === $expectedPosition;
    }

    protected function endsWith($haystack, $needle, $case=true): bool
    {
        $expectedPosition = strlen($haystack) - strlen($needle);

        if ($case) {
            return strrpos($haystack, $needle, 0) === $expectedPosition;
        }

        return strripos($haystack, $needle, 0) === $expectedPosition;
    }

    protected function extractLessonSlug(string $url) : string
    {
        $matches = array();
        if (false !== preg_match("!/lessons/(?P<slug>[^#?&]+)!", $url, $matches)) {
            return $matches['slug'];
        }

        return "";
    }
}
