<?php

/**
 * This file is part of the Latte (https://latte.nette.org)
 * Copyright (c) 2008 David Grudl (https://davidgrudl.com)
 */
declare (strict_types=1);
namespace Packetery\Latte\Tools;

use Packetery\Latte;
use Packetery\Nette;
final class Linter
{
    use \Packetery\Latte\Strict;
    /** @var bool */
    private $debug;
    /** @var \Packetery\Latte\Engine|null */
    private $engine;
    public function __construct(?Latte\Engine $engine = null, bool $debug = \false)
    {
        $this->engine = $engine;
        $this->debug = $debug;
    }
    public function scanDirectory(string $dir) : bool
    {
        echo "Scanning {$dir}\n";
        $it = new \RecursiveDirectoryIterator($dir);
        $it = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::LEAVES_ONLY);
        $it = new \RegexIterator($it, '~\\.latte$~');
        $this->engine = $this->engine ?? $this->createEngine();
        $this->engine->setLoader(new \Packetery\Latte\Loaders\StringLoader());
        $counter = 0;
        $success = \true;
        foreach ($it as $file) {
            echo \str_pad(\str_repeat('.', $counter++ % 40), 40), "\r";
            $success = $this->lintLatte((string) $file) && $success;
        }
        echo \str_pad('', 40), "\r";
        echo "Done.\n";
        return $success;
    }
    private function createEngine() : \Packetery\Latte\Engine
    {
        $engine = new \Packetery\Latte\Engine();
        if (\class_exists(Nette\Bridges\CacheLatte\CacheMacro::class)) {
            $engine->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro());
        }
        if (\class_exists(Nette\Bridges\ApplicationLatte\UIMacros::class)) {
            Nette\Bridges\ApplicationLatte\UIMacros::install($engine->getCompiler());
        }
        if (\class_exists(Nette\Bridges\FormsLatte\FormMacros::class)) {
            Nette\Bridges\FormsLatte\FormMacros::install($engine->getCompiler());
        }
        return $engine;
    }
    public function lintLatte(string $file) : bool
    {
        \set_error_handler(function (int $severity, string $message) use($file) {
            if (\in_array($severity, [\E_USER_DEPRECATED, \E_USER_WARNING, \E_USER_NOTICE], \true)) {
                $pos = \preg_match('~on line (\\d+)~', $message, $m) ? ':' . $m[1] : '';
                \fwrite(\STDERR, "[DEPRECATED] {$file}{$pos}    {$message}\n");
                return null;
            }
            return \false;
        });
        if ($this->debug) {
            echo $file, "\n";
        }
        $s = \file_get_contents($file);
        if (\substr($s, 0, 3) === "﻿") {
            \fwrite(\STDERR, "[WARNING]    {$file}    contains BOM\n");
        }
        try {
            $code = $this->engine->compile($s);
        } catch (\Packetery\Latte\CompileException $e) {
            if ($this->debug) {
                echo $e;
            }
            $pos = $e->sourceLine ? ':' . $e->sourceLine : '';
            \fwrite(\STDERR, "[ERROR]      {$file}{$pos}    {$e->getMessage()}\n");
            return \false;
        } finally {
            \restore_error_handler();
        }
        if ($error = $this->lintPHP($code)) {
            \fwrite(\STDERR, "[ERROR]      {$file}    {$error}\n");
            return \false;
        }
        return \true;
    }
    private function lintPHP(string $code) : ?string
    {
        $php = \defined('PHP_BINARY') ? \PHP_BINARY : 'php';
        $stdin = \tmpfile();
        \fwrite($stdin, $code);
        \fseek($stdin, 0);
        $process = \proc_open($php . ' -l -d display_errors=1', [$stdin, ['pipe', 'w'], ['pipe', 'w']], $pipes, null, null, ['bypass_shell' => \true]);
        if (!\is_resource($process)) {
            return 'Unable to lint PHP code';
        }
        $error = \stream_get_contents($pipes[1]);
        if (\proc_close($process)) {
            return \strip_tags(\explode("\n", $error)[1]);
        }
        return null;
    }
}
