<?php

namespace Snapshots;

use Imagick;
use Mpdf\Mpdf;

/**
 * @group snapshot
 */
abstract class Snapshot extends \Yoast\PHPUnitPolyfills\TestCases\TestCase
{
	/**
	 * @var Mpdf
	 */
	protected $mpdf;

	/**
	 * @return string A unique identifier / name for the snapshot
	 */
	abstract public function getId();

	/**
	 * Generate a PDF document by initializing the Mpdf object on $this->mpdf and
	 * loading it with content
	 *
	 * @return   void
	 * @internal Don't call any $this->mpdf->Output*() method
	 */
	abstract public function generatePdf();

	/**
	 * @return float The fuzzy comparison to use when comparing the snapshots
	 */
	protected function getComparisonFuzzyLimit()
	{
		return 0.0;
	}

	protected function getPathToSnapshot()
	{
		return __DIR__ . '/../data/snapshots/';
	}

	/**
	 * Test the snapshot
	 *
	 * @return void
	 * @throws ImagickException
	 */
	public function testSnapshot()
	{
		if (! class_exists('\Imagick')) {
			$this->markTestSkipped('PDF snapshot testing requires the \Imagick PECL module.');
		}

		$this->generatePdf();
		if (! $this->mpdf instanceof Mpdf) {
			$this->markTestIncomplete('$this->mpdf is not an Mpdf object.');
		}

		/* Generate a new snapshot using the current codebase */
		$temporaryFile = tempnam(sys_get_temp_dir(), 'Pdf');
		$this->mpdf->OutputFile($temporaryFile);

		/* Create artifact directory for failed test results */
		$failedDir = $this->mpdf->tempDir . '/artifacts/' . $this->getName(false) . '/';
		if (! is_dir($failedDir)) {
			mkdir($failedDir, 0755, true);
		}

		$failed = [];

		try {
			/**
			 * Convert the original and new PDF into images, and then compare each image
			 *
			 * Note: a PDF snapshot is saved instead of the images because comparing images generated by
			 * different version of Imagick will cause errors.
			 *
			 * @see https://github.com/mpdf/mpdf/pull/197
			 */
			$originalSnapshotImages  = $this->generateImagesFromPdf($this->getPathToSnapshot() . $this->getId() . '.pdf');
			$temporarySnapshotImages = $this->generateImagesFromPdf($temporaryFile);

			foreach ($temporarySnapshotImages as $i => $image) {
				$pageNo = $i + 1;
				if (! isset($originalSnapshotImages[ $i ])) {
					$failed[] = sprintf('Page #%s does not exist in the original PDF snapshot', $pageNo);
					continue;
				}

				$image1 = new Imagick($image);
				$image2 = new Imagick($originalSnapshotImages[ $i ]);

				$result = $image1->compareImages($image2, Imagick::METRIC_ABSOLUTEERRORMETRIC);

				/* Cleanup Imagick */
				$image1->clear();
				$image2->clear();

				unset($image1, $image2);

				/* If comparison result over the fuzzy limit, save composition image */
				if ($result[1] > $this->getComparisonFuzzyLimit()) {
					$filename = sprintf('snapshot-%s-page-%d.png', $this->getId(), $pageNo);
					$failed[] = sprintf('Page #%s of the document "%s" has discrepancies.', $pageNo, $filename);

					$result[0]->setImageFormat('png');
					file_put_contents($failedDir . $filename, $result[0]);
				}

				unset($result);
			}

			if (count($failed) > 0) {
				throw new \Exception(); // throw empty exception so assetCount() shows the errors
			}
		} catch (\Exception $e) {
			/* Copy generated PDF to artifact folder for later review */
			copy($temporaryFile, $failedDir . $this->getId() . '.pdf');
			if (! empty($e->getMessage())) {
				$failed[] = $e->getMessage();
			}
		}

		$this->assertCount(0, $failed, implode("\n", $failed));
	}

	protected function generateImagesFromPdf($pdf)
	{
		$pdf = realpath($pdf);
		if (! $pdf || !is_readable($pdf)) {
			throw new \Exception('PDF not readable:' . $pdf);
		}

		$img = new Imagick();
		$img->setResolution(120, 120); //ensure better quality of the text
		$img->readImage($pdf);
		$img->setImageBackgroundColor('white'); //prevents black background on any objects with transparency
		$img->setImageCompressionQuality(100);

		$page_no = $img->getNumberImages();

		/* Loops through each page in the PDF and creates the image */
		$generated = [];
		for ($i = 0; $i < $page_no; $i ++) {
			$temp_image = tempnam(sys_get_temp_dir(), 'PdfImage');

			$img->setIteratorIndex($i); //set iterator position
			$img->setImageFormat('png');
			$img->writeImage($temp_image);
			$generated[] = $temp_image;
		}

		$img->clear();
		unset($img);

		return $generated;
	}
}
