<?php

/**
 * Vvveb
 *
 * Copyright (C) 2022  Ziadin Givan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

/*
Name: Minify css and js
Slug: minify
Category: performance
Url: https://www.vvveb.com
Description: Minify javascript and css for better frontend performance.
Author: givanz
Version: 0.1
Thumb: minify.svg
Author url: https://www.vvveb.com
*/

use function Vvveb\__;
use function Vvveb\isEditor;
use function Vvveb\sanitizeFileName;
use function Vvveb\slugify;
use Vvveb\System\Event;

if (! defined('V_VERSION')) {
	die('Invalid request!');
}

define('MINIFY_JS', true);
define('MINIFY_CSS', true);

class MinifyPlugin {
	//load minifier files only when minifying to avoid bloat loading them on every page request
	function initMinifier() {
		if (MINIFY_JS || MINIFY_CSS) {
			$path = __DIR__ . DS . 'system' . DS;

			require_once $path . '/minify/Minify.php';

			require_once $path . '/minify/CSS.php';

			require_once $path . '/minify/JS.php';

			require_once $path . '/minify/Exception.php';

			require_once $path . '/minify/Exceptions/BasicException.php';

			require_once $path . '/minify/Exceptions/FileImportException.php';

			require_once $path . '/minify/Exceptions/IOException.php';

			require_once $path . '/minify/ConverterInterface.php';

			require_once $path . '/minify/Converter.php';
		}
	}

	function admin() {
		//add admin menu item
		$admin_path = \Vvveb\adminPath();
		Event::on('Vvveb\Controller\Base', 'init-menu', __CLASS__, function ($menu) use ($admin_path) {
			$menu['plugins']['items']['minify-plugin'] = [
				'name'     => __('Minify'),
				'url'      => '/admin/',
				'icon-img' => PUBLIC_PATH . 'plugins/minify/minify.svg',
			];

			return [$menu];
		});
	}

	function minifyJs($js, $path = '') {
		$minifier = new MatthiasMullie\Minify\JS();
		$minifier->add($js);

		return $minifier->minify();
	}

	function minifyCss($css, $path = '') {
		$minifier = new MatthiasMullie\Minify\CSS();
		$minifier->add($css);

		return $minifier->minify();
	}

	function processJs(&$vTpl, $template, $theme, $prefixSelector = 'head > ') {
		$inlineJs = '';
		/*
		$scripts = $vTpl->query($prefixSelector . 'script');
		foreach ($scripts as $style) {
			$inlineCss .= trim($style->textContent);
		}
		*/

		$scripts = $vTpl->query($prefixSelector . 'script');
		$length  = $scripts->length;

		if ($length > 1) {
			$fileName         = $theme . '-';
			$content          = '';
			$names            = [];
			$customCss        = '';
			$moveInlineBottom = true;

			$document = $vTpl->getDocument();

			if (MINIFY_JS) {
				$minifier = new MatthiasMullie\Minify\JS();
			}

			$i             = 0;
			$lastScript    = null;
			$removeScripts = [];

			foreach ($scripts as $link) {
				$href = $link->getAttribute('src');
				//skip external js
				if (strncmp($href, 'http', 4) === 0) {
					$length--;

					continue;
				}

				//skip no minify
				if ($link->hasAttribute('data-no-minify')) {
					$length--;

					continue;
				}

				//skip inline
				if (! $link->hasAttribute('src')) {
					if ($moveInlineBottom) {
						$body = $document->getElementsByTagName('body')->item(0);
						$body->appendChild($link);
					}
					$length--;

					continue;
				}

				//skip module
				if ($link->hasAttribute('type') &&
					(($type = $link->getAttribute('type') == 'module') || ($type = 'application/ld+json') || ($type = 'speculationrules'))
				) {
					$length--;

					continue;
				}

				$i++;
				$lastScript = $link;

				$path = DIR_THEMES . $theme . DS;
				$href = str_replace(PUBLIC_PATH . $theme . '/', '', $href);

				//absolute path or upper level then use public folder
				if ($href && ($href[0] == '/' || strncmp($href, '../../', 6) === 0)) {
					$path = DIR_PUBLIC;
				}

				$file = $path . sanitizeFileName($href);

				if (! file_exists($file)) {
					$i--;

					continue;
				}

				if (MINIFY_JS) {
					//$styles = $this->minifyJs($styles, $href);
					$minifier->add($file);
				} else {
					//$content .= "\n/* $href */\n\n" . $styles;
					$styles = file_get_contents($file);
					$content .= "\n" . $styles;
				}

				$names[] = slugify(basename(str_replace('.min', '', $href), '.js'));
				//replace last link with minified css
				if ($i >= $length) {
				} else {
					//$link->remove();
				}

				$removeScripts[] = $link;
			}

			if ($lastScript) {
				sort($names);
				$fileName .= implode('-', $names) . '.js';

				if ($inlineJs) {
					//$content .= "\n/* -- inline - */\n\n" . $inlineJs;
					$content .= "\n" . $inlineJs;
				}

				//if (file_put_contents(DIR_PUBLIC . DS . 'assets-cache' . DS . $fileName, $content)) {
				if ($lastScript && $lastScript->parentNode && $minifier->minify(DIR_PUBLIC . DS . 'assets-cache' . DS . $fileName)) {
					$minified = $document->createElement('script');
					$minified->setAttribute('src', '/assets-cache/' . $fileName);
					$lastScript->parentNode->replaceChild($minified, $lastScript);
					//if minified succeeded remove all scripts
					foreach ($removeScripts as $script) {
						if (! $script->isSameNode($lastScript)) {
							//$script->remove();
							$script->parentNode->removeChild($script);
						}
					}
					//remove inline js
					/*
					$inlineCss = $vTpl->query($prefixSelector . 'script');
					foreach ($inlineCss as $style) {
						$style->remove();
					}*/
				}
			}
		}
	}

	function processCss(&$vTpl, $template, $theme, $prefixSelector = 'head > ') {
		$css       = $vTpl->query($prefixSelector . 'style');
		$inlineCss = '';

		foreach ($css as $style) {
			$inlineCss .= trim($style->textContent);
		}

		$css    = $vTpl->query($prefixSelector . 'link[rel="stylesheet"]');
		$length = $css->length;

		if ($length > 1) {
			$fileName  = $theme . '-';
			$content   = '';
			$names     = [];
			$customCss = '';

			$document = $vTpl->getDocument();

			if (MINIFY_CSS) {
				$minifier = new MatthiasMullie\Minify\CSS();
			}

			$i         = 0;
			$lastStyle = null;

			foreach ($css as $link) {
				$href = $link->getAttribute('href');
				//skip external css
				if (! $href || (strncmp($href, 'http', 4) === 0)) {
					$length--;

					continue;
				}

				//skip no minify
				if ($link->hasAttribute('data-no-minify')) {
					$length--;

					continue;
				}

				$i++;

				$path = DIR_THEMES . $theme . DS;
				$href = str_replace(PUBLIC_PATH . $theme . '/', '', $href);

				//absolute path or upper level then use public folder
				if ($href && ($href[0] == '/' || strncmp($href, '../../', 6) === 0)) {
					$path = DIR_PUBLIC;
				}

				$file = $path . sanitizeFileName($href);

				if (! file_exists($file)) {
					$i--;

					continue;
				}

				if (MINIFY_CSS) {
					//$styles = $this->minifyJs($styles, $href);
					$minifier->add($file);
				} else {
					//$content .= "\n/* $href */\n\n" . $styles;
					$styles = file_get_contents($file);
				}

				//add custom css last for highest priority
				if (substr_compare($href, 'custom.css', -10, 10) === 0) {
					if (file_exists($file)) {
						$customCss = file_get_contents($file);
					}
				} else {
					//$content .= "\n/* $href */\n\n" . $styles;
					if (! MINIFY_CSS) {
						$content .= "\n" . $styles;
					}
				}

				$names[]   = slugify(basename(str_replace('.min', '', $href), '.css'));
				$lastStyle = $link;
				//replace last link with minified css
				if ($i >= ($length - 1)) {
				} else {
					//$link->remove();
					$link->parentNode->removeChild($link);
				}
			}

			if ($lastStyle/* && ($i >= $length)*/) {
				sort($names);
				$fileName .= implode('-', $names) . '.css';

				if ($inlineCss) {
					if (MINIFY_CSS) {
						$minifier->add($inlineCss);
					} else {
						//$content .= "\n/* -- inline - */\n\n" . $inlineCss;
						$content .= "\n" . $inlineCss;
					}
				}

				if ($customCss) {
					if (MINIFY_CSS) {
						$minifier->add($inlineCss);
					} else {
						//$content .= "\n/* -- custom.css - */\n\n" . $customCss;
						$content .= "\n" . $customCss;
					}
				}

				//if (file_put_contents(DIR_PUBLIC . DS . 'assets-cache' . DS . $fileName, $content)) {
				if ($lastStyle && $lastStyle->parentNode && $minifier->minify(DIR_PUBLIC . DS . 'assets-cache' . DS . $fileName)) {
					$minified = $document->createElement('link');
					$minified->setAttribute('rel','stylesheet');
					$minified->setAttribute('href', '/assets-cache/' . $fileName);

					$lastStyle->parentNode->replaceChild($minified, $lastStyle);
					//remove inline css
					$inlineCss = $vTpl->query($prefixSelector . 'style');

					foreach ($inlineCss as $style) {
						//$style->remove();
						$style->parentNode->removeChild($style);
					}
				}
			}
		}
	}

	function app() {
		//don't minify if page is opened in editor
		if (isEditor()) {
			return;
		}
		Event::on('Vvveb\System\Core\View', 'compile:after', __CLASS__, function ($template, $htmlFile, $tplFile, $vTpl, $view) {
			$theme = $view->getTheme();

			$this->initMinifier();

			$this->processCss($vTpl, $template, $theme);
			$this->processCss($vTpl, $template, $theme, 'body > ');

			$this->processJs($vTpl, $template, $theme);
			$this->processJs($vTpl, $template, $theme, 'body > ');

			return [$template, $htmlFile, $tplFile, $vTpl, $view];
		});
	}

	function __construct() {
		if (APP == 'admin') {
			$this->admin();
		} else {
			if (APP == 'app') {
				$this->app();
			}
		}
	}
}

$minifyPlugin = new MinifyPlugin();
