<?php
/**
 * A doc generator that outputs documentation in Markdown format.
 *
 * @author    Stefano Kowalke <blueduck@gmx.net>
 * @author    Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
 * @copyright 2014 Arroba IT
 * @copyright 2024 PHPCSStandards and contributors
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
 */

namespace PHP_CodeSniffer\Generators;

use DOMDocument;
use DOMNode;
use PHP_CodeSniffer\Config;

class Markdown extends Generator
{


    /**
     * Generates the documentation for a standard.
     *
     * @return void
     * @see    processSniff()
     */
    public function generate()
    {
        if (empty($this->docFiles) === true) {
            return;
        }

        ob_start();
        $this->printHeader();

        foreach ($this->docFiles as $file) {
            $doc = new DOMDocument();
            $doc->load($file);
            $documentation = $doc->getElementsByTagName('documentation')->item(0);
            $this->processSniff($documentation);
        }

        $this->printFooter();
        $content = ob_get_contents();
        ob_end_clean();

        echo $content;

    }//end generate()


    /**
     * Print the markdown header.
     *
     * @return void
     */
    protected function printHeader()
    {
        $standard = $this->ruleset->name;

        echo "# $standard Coding Standard".PHP_EOL;

    }//end printHeader()


    /**
     * Print the markdown footer.
     *
     * @return void
     */
    protected function printFooter()
    {
        // Turn off errors so we don't get timezone warnings if people
        // don't have their timezone set.
        $errorLevel = error_reporting(0);
        echo PHP_EOL.'Documentation generated on '.date('r');
        echo ' by [PHP_CodeSniffer '.Config::VERSION.'](https://github.com/PHPCSStandards/PHP_CodeSniffer)'.PHP_EOL;
        error_reporting($errorLevel);

    }//end printFooter()


    /**
     * Process the documentation for a single sniff.
     *
     * @param \DOMNode $doc The DOMNode object for the sniff.
     *                      It represents the "documentation" tag in the XML
     *                      standard file.
     *
     * @return void
     */
    protected function processSniff(DOMNode $doc)
    {
        $title = $this->getTitle($doc);
        echo PHP_EOL."## $title".PHP_EOL.PHP_EOL;

        foreach ($doc->childNodes as $node) {
            if ($node->nodeName === 'standard') {
                $this->printTextBlock($node);
            } else if ($node->nodeName === 'code_comparison') {
                $this->printCodeComparisonBlock($node);
            }
        }

    }//end processSniff()


    /**
     * Print a text block found in a standard.
     *
     * @param \DOMNode $node The DOMNode object for the text block.
     *
     * @return void
     */
    protected function printTextBlock(DOMNode $node)
    {
        $content = trim($node->nodeValue);
        $content = htmlspecialchars($content, (ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401));
        $content = str_replace('&lt;em&gt;', '*', $content);
        $content = str_replace('&lt;/em&gt;', '*', $content);

        $nodeLines = explode("\n", $content);
        $lineCount = count($nodeLines);
        $lines     = [];

        for ($i = 0; $i < $lineCount; $i++) {
            $currentLine = trim($nodeLines[$i]);
            if ($currentLine === '') {
                // The text contained a blank line. Respect this.
                $lines[] = '';
                continue;
            }

            // Check if the _next_ line is blank.
            if (isset($nodeLines[($i + 1)]) === false
                || trim($nodeLines[($i + 1)]) === ''
            ) {
                // Next line is blank, just add the line.
                $lines[] = $currentLine;
            } else {
                // Ensure that line breaks are respected in markdown.
                $lines[] = $currentLine.'  ';
            }
        }

        echo implode(PHP_EOL, $lines).PHP_EOL;

    }//end printTextBlock()


    /**
     * Print a code comparison block found in a standard.
     *
     * @param \DOMNode $node The DOMNode object for the code comparison block.
     *
     * @return void
     */
    protected function printCodeComparisonBlock(DOMNode $node)
    {
        $codeBlocks = $node->getElementsByTagName('code');

        $firstTitle = trim($codeBlocks->item(0)->getAttribute('title'));
        $firstTitle = str_replace('  ', '&nbsp;&nbsp;', $firstTitle);
        $first      = trim($codeBlocks->item(0)->nodeValue);
        $first      = str_replace("\n", PHP_EOL.'    ', $first);
        $first      = str_replace('<em>', '', $first);
        $first      = str_replace('</em>', '', $first);

        $secondTitle = trim($codeBlocks->item(1)->getAttribute('title'));
        $secondTitle = str_replace('  ', '&nbsp;&nbsp;', $secondTitle);
        $second      = trim($codeBlocks->item(1)->nodeValue);
        $second      = str_replace("\n", PHP_EOL.'    ', $second);
        $second      = str_replace('<em>', '', $second);
        $second      = str_replace('</em>', '', $second);

        echo '  <table>'.PHP_EOL;
        echo '   <tr>'.PHP_EOL;
        echo "    <th>$firstTitle</th>".PHP_EOL;
        echo "    <th>$secondTitle</th>".PHP_EOL;
        echo '   </tr>'.PHP_EOL;
        echo '   <tr>'.PHP_EOL;
        echo '<td>'.PHP_EOL.PHP_EOL;
        echo "    $first".PHP_EOL.PHP_EOL;
        echo '</td>'.PHP_EOL;
        echo '<td>'.PHP_EOL.PHP_EOL;
        echo "    $second".PHP_EOL.PHP_EOL;
        echo '</td>'.PHP_EOL;
        echo '   </tr>'.PHP_EOL;
        echo '  </table>'.PHP_EOL;

    }//end printCodeComparisonBlock()


}//end class
