#!/usr/bin/php
<?php

session_start();

function __autoload($className): void { require_once __DIR__."/../libraries/".$className.".class.php"; }
require_once __DIR__."/../libraries/constants.inc.php";
require_once __DIR__."/../configuration.php";
require_once __DIR__."/../libraries/commonFunctions.inc.php";
require_once __DIR__."/../libraries/registerExceptions.inc.php";

class CacheControl extends ShellExecutable  {

	private $lockFile;
	private $successCount = 0;
	private $failedCount = 0;
	private $removedCount = 0;
	private $ignoredCount = 0;
	private $blacklist = [];

	static public function getNewInstance(ShellInOutConnector $console): self {
		return new self($console);
	}

	public function prepare(): void {
		$this->lockFile = __DIR__.'/'.TEMPORARY_DIRECTORY."/generateThumbs.lock";
		$this->setTitle('cache', "Warms up / clears cache of the designer. Cached thumbnails for existing documents in the library are skipped.");
		$this->addParameter('command', 'clear / warmup');
		$this->addSwitch('i', 'ignore-lock', 'ignore lock');
		$this->addSwitch('a', 'all', 'also remove cached thumbnails for existing documents in the library.');
		$this->addSwitch('f', 'skip-finished', 'skip the finished directory when warming / remove thumbnails of it when clearing.');
		$this->addSwitch('p', 'skip-private', 'skip the private directory when warming / remove thumbnails of it when clearing.');
		$this->addSwitch('v', 'verbose', 'print out status information and detailed error message when conversion fails');
		$this->addSwitch('dim', 'dimensions', 'generate thumbs with this width / eg. 800x600', '');
	}

	/**
	 * (non-PHPdoc)
	 * @see ShellExecutable::checkParameter()
	 */
	public function checkParameter($name, $value) {
		if ($name == 'command') {
			if (!in_array($value, ['clear', 'warmup'])) {
				return 'Unknown command "'.$value.'"';
			}
		}
	}

	/**
	 * (non-PHPdoc)
	 * @see ShellExecutable::execute()
	 */
	public function execute(): void {
		if ($this->checkRunning()) {
			$this->quit("This tool is already running (lockfile was found). Use option -i to ignore.");
		}
		$this->recurseDirectory(rtrim(LIBRARY_PATH,          "/"));
		$this->recurseDirectory(rtrim(HOME_DIRECTORIES_PATH, "/"));
		if ($this->getParameter('command') == 'warmup') {
			$this->message($this->successCount . " images converted (or already cached)");
			if ($this->failedCount > 0) {
				$this->messageLine(", " . $this->failedCount . " images failed to convert (Use -v to get error message).", "RED");
			}
			$this->newLine();
		} else {
			$this->clearCache();
		}
		$this->removeLock();
		exit(0);
	}

	/**
	 * Clear cache files: thumbnails and interim PDFs
	 */
	private function clearCache(): void {
		$this->clearThumbnails();
		$this->clearPDFs();
		$this->messageLine($this->removedCount . " files removed. ". $this->ignoredCount . " files skipped.");
	}

	/**
	 * Clear thumbails (pngs)
	 */
	private function clearThumbnails(): void {
		$directoryPath = TEMPORARY_DIRECTORY.'/ImageMagick/cache';
		if (!$directory = dir($directoryPath)) { $this->error("could not read ".$directoryPath); }
		while ($entryName = $directory->read()) {
			if ($entryName == "." or $entryName == "..") { continue; }
			$this->remove($directoryPath."/".$entryName);
		}
	}

	/**
	 * Clear interim PDFs
	 */
	private function clearPDFs(): void {
		$directoryPath = TEMPORARY_DIRECTORY.'/ImageMagick';
		if (!$directory = dir($directoryPath)) { $this->error("could not read ".$directoryPath); }
		while ($entryName = $directory->read()) {
			if ($entryName == "." or $entryName == "..") { continue; }
			if (get_extension($entryName) !== 'pdf') { continue; }
			$this->remove($directoryPath."/".$entryName);
		}
	}

	/**
	 * Remove a file if not on blacklist and display message.
	 *
	 * @param string $filePath
	 */
	private function remove(string $filePath): void {
		if (!$this->isSwitch('a') && in_array($filePath, $this->blacklist)) {
			if ($this->isSwitch('v')) {
				$this->messageLine($filePath . " ... skipped.");
			}
			$this->ignoredCount ++;
		} else {
			$this->message($filePath . " ... ");
			if (! @unlink($filePath)) {
				$this->messageLine("REMOVE FAILED", "RED");
			} else {
				$this->messageLine("REMOVED", "GREEN");
				$this->removedCount ++;
			}
		}
	}

	/**
	 * Convert all files in specified direcory and it's subdirectories.
	 *
	 * @param string $directoryPath
	 */
	private function recurseDirectory(string $directoryPath): void {
		if (!is_dir($directoryPath)) { return; }
		if ($this->isSwitch('f') && rtrim($directoryPath, '/') == rtrim(ACTIVE_DOCUMENTS_DIRECTORY, '/')) {
			if ($this->isSwitch('v')) {
				$this->messageLine('skipping finished directory '.$directoryPath);
			}
			return;
		}
		if ($this->isSwitch('p') && rtrim($directoryPath, '/') == rtrim(HOME_DIRECTORIES_PATH, '/')) {
			if ($this->isSwitch('v')) {
				$this->messageLine('skipping private directory '.$directoryPath);
			}
			return;
		}
		$designerAccess = rtrim($directoryPath, '/')."/.designerAccess";
		if ($this->getParameter('command') == 'warmup' && file_exists($designerAccess) && strpos(file_get_contents($designerAccess), "cache nobody") !== FALSE) {
			if ($this->isSwitch('v')) {
				$this->messageLine('skipping no-cache directory '.$directoryPath);
			}
			return;
		}
		if ($this->isSwitch('v')) {
			$this->messageLine("proceeding directory ".$directoryPath);
		}
		if (!$directory = dir($directoryPath)) { $this->error("could not read ".$directoryPath); }
		while ($entryName = $directory->read()) {
			if ($entryName == "." or $entryName == ".." or $entryName == ".designerAccess") { continue; }
			$filePath = $directoryPath."/".$entryName;
			if (is_dir($filePath)) {
				$skipProcess = file_exists($filePath."/.designerAccess") && strpos(file_get_contents($filePath."/.designerAccess"), "cache nobody") !== FALSE;
				if (file_exists($filePath."/.structure.xml") && !$skipProcess) {
					$this->proceedFile($filePath."/.structure.xml");
				} else {
					$this->recurseDirectory($filePath);
				}
			} else {
				$this->proceedFile($filePath);
			}
		}
	}

	/**
	 * Resize the file to the three sizes like the Client
	 *
	 * @param string $filePath
	 */
	private function proceedFile(string $filePath): void {
		if ($this->getParameter('command') == 'warmup') { $this->message($filePath." "); }
		$dimensionsParameter = $this->isSwitch('dim') ? $this->getSwitch('dim') : '';
		if (!empty($dimensionsParameter) && preg_match('/^[\d]+x[\d]+$/', $dimensionsParameter)) {
			[$width, $height] = explode('x', $dimensionsParameter);
			$returnValue = $this->resizeFile($filePath, $width, $height);
		} else {
			$returnValue = $this->resizeFile($filePath, 70, 50) && $this->resizeFile($filePath, 140, 100) && $this->resizeFile($filePath, 280, 200);
		}
		if ($returnValue) {
			if ($this->getParameter('command') == 'warmup') { $this->messageLine(" OK", "GREEN"); }
			$this->successCount ++;
		}
	}

	/**
	 * Resize and cache the file like the Web2Client. See src/.htaccess-dist, src/services/getImage.php
	 *
	 * @param string $filePath
	 * @param int $width
	 * @param int $height
	 */
	private function resizeFile($filePath, $width, $height): bool {
		$imageService = new ImageService($filePath);
		$imageService->setColorspaceConversion("RGB", COLORPROFILE_CMYK, COLORPROFILE_RGB);
		$imageService->setDensity(PDF_DENSITY);
		$imageService->addExternalConverter(new DesignerDocumentExternalConverter());
		$imageService->setAllowedDirectories([TEMPORARY_DIRECTORY, LIBRARY_PATH, HOME_DIRECTORIES_PATH, ADDITIONAL_ALLOWED_PATH]);
		$parameters = ['cache'   => 1, 'action'  => 'convert', 'format'  =>'png', 'quality' => 100, 'action2' => 'resize', 'width'   => $width, 'height'  => $height];
		$imageService->setParameters($parameters);
		if ($this->getParameter('command') == 'warmup') {
			$imageService->generate();

			if ($imageService->isError()) {
				if ($this->isSwitch('v')) {
					$this->messageLine("FAILED: " . $imageService->getError(), 'RED');
				} else {
					$this->messageLine("FAILED", "RED");
				}
				$this->failedCount ++;
				return false;
			} else {
				$this->message('.');
				return true;
			}
		} else {
			$this->blacklist[] = $imageService->getCacheFilePath();
			return true;
		}
	}

	/**
	 * Check if there is another instance running
	 */
	private function checkRunning(): bool {
		if ($this->isSwitch('i')) { return false; }
		if (file_exists($this->lockFile)) { return true; }
		touch($this->lockFile);
		return false;
	}

	/**
	 * Remove the lock file
	 */
	private function removeLock(): void {
		if (!$this->isSwitch('i')) {
			unlink($this->lockFile);
		}
	}
}

CacheControl::getNewInstance(new ShellInOutConnector(1000))->execute();
