#!/usr/bin/env php
<?php

use dokuwiki\Utf8\Sort;
use dokuwiki\File\PageResolver;
use splitbrain\phpcli\CLI;
use splitbrain\phpcli\Options;

if (!defined('DOKU_INC')) define('DOKU_INC', realpath(__DIR__ . '/../') . '/');
define('NOSESSION', 1);
require_once(DOKU_INC . 'inc/init.php');

/**
 * Find wanted pages
 */
class WantedPagesCLI extends CLI
{
    protected const DIR_CONTINUE = 1;
    protected const DIR_NS = 2;
    protected const DIR_PAGE = 3;

    private $skip = false;
    private $sort = 'wanted';

    private $result = [];

    /**
     * Register options and arguments on the given $options object
     *
     * @param Options $options
     * @return void
     */
    protected function setup(Options $options)
    {
        $options->setHelp(
            'Outputs a list of wanted pages (pages that do not exist yet) and their origin pages ' .
            ' (the pages that are linkin to these missing pages).'
        );
        $options->registerArgument(
            'namespace',
            'The namespace to lookup. Defaults to root namespace',
            false
        );

        $options->registerOption(
            'sort',
            'Sort by wanted or origin page',
            's',
            '(wanted|origin)'
        );

        $options->registerOption(
            'skip',
            'Do not show the second dimension',
            'k'
        );
    }

    /**
     * Your main program
     *
     * Arguments and options have been parsed when this is run
     *
     * @param Options $options
     * @return void
     */
    protected function main(Options $options)
    {
        $args = $options->getArgs();
        if ($args) {
            $startdir = dirname(wikiFN($args[0] . ':xxx'));
        } else {
            $startdir = dirname(wikiFN('xxx'));
        }

        $this->skip = $options->getOpt('skip');
        $this->sort = $options->getOpt('sort');

        $this->info("searching $startdir");

        foreach ($this->getPages($startdir) as $page) {
            $this->internalLinks($page);
        }
        Sort::ksort($this->result);
        foreach ($this->result as $main => $subs) {
            if ($this->skip) {
                echo "$main\n";
            } else {
                $subs = array_unique($subs);
                Sort::sort($subs);
                foreach ($subs as $sub) {
                    printf("%-40s %s\n", $main, $sub);
                }
            }
        }
    }

    /**
     * Determine directions of the search loop
     *
     * @param string $entry
     * @param string $basepath
     * @return int
     */
    protected function dirFilter($entry, $basepath)
    {
        if ($entry == '.' || $entry == '..') {
            return WantedPagesCLI::DIR_CONTINUE;
        }
        if (is_dir($basepath . '/' . $entry)) {
            if (str_starts_with($entry, '_')) {
                return WantedPagesCLI::DIR_CONTINUE;
            }
            return WantedPagesCLI::DIR_NS;
        }
        if (preg_match('/\.txt$/', $entry)) {
            return WantedPagesCLI::DIR_PAGE;
        }
        return WantedPagesCLI::DIR_CONTINUE;
    }

    /**
     * Collects recursively the pages in a namespace
     *
     * @param string $dir
     * @return array
     * @throws DokuCLI_Exception
     */
    protected function getPages($dir)
    {
        static $trunclen = null;
        if (!$trunclen) {
            global $conf;
            $trunclen = strlen($conf['datadir'] . ':');
        }

        if (!is_dir($dir)) {
            throw new DokuCLI_Exception("Unable to read directory $dir");
        }

        $pages = [];
        $dh = opendir($dir);
        while (false !== ($entry = readdir($dh))) {
            $status = $this->dirFilter($entry, $dir);
            if ($status == WantedPagesCLI::DIR_CONTINUE) {
                continue;
            } elseif ($status == WantedPagesCLI::DIR_NS) {
                $pages = array_merge($pages, $this->getPages($dir . '/' . $entry));
            } else {
                $page = ['id' => pathID(substr($dir . '/' . $entry, $trunclen)), 'file' => $dir . '/' . $entry];
                $pages[] = $page;
            }
        }
        closedir($dh);
        return $pages;
    }

    /**
     * Parse instructions and add the non-existing links to the result array
     *
     * @param array $page array with page id and file path
     */
    protected function internalLinks($page)
    {
        global $conf;
        $instructions = p_get_instructions(file_get_contents($page['file']));
        $resolver = new PageResolver($page['id']);
        $pid = $page['id'];
        foreach ($instructions as $ins) {
            if ($ins[0] == 'internallink' || ($conf['camelcase'] && $ins[0] == 'camelcaselink')) {
                $mid = $resolver->resolveId($ins[1][0]);
                if (!page_exists($mid)) {
                    [$mid] = explode('#', $mid); //record pages without hashes

                    if ($this->sort == 'origin') {
                        $this->result[$pid][] = $mid;
                    } else {
                        $this->result[$mid][] = $pid;
                    }
                }
            }
        }
    }
}

// Main
$cli = new WantedPagesCLI();
$cli->run();
