<?php

namespace XF\Service\AddOn;

use XF\AddOn\AddOn;
use XF\App;
use XF\Service\AbstractService;
use XF\Util\File;
use XF\Util\Hash;
use XF\Util\Json;

class HashGeneratorService extends AbstractService
{
	/**
	 * @var AddOn
	 */
	protected $addOn;

	protected $rootPath;
	protected $hashesPath;

	protected $writeHashes = true;

	protected $filesToHash = [];
	protected $filesPrepared = false;

	public function __construct(App $app, AddOn $addOn, $rootPath, $hashesPath = null)
	{
		parent::__construct($app);

		$this->addOn = $addOn;
		$this->rootPath = $rootPath;
		$this->hashesPath = $hashesPath;
	}

	public function setWriteHashes($writeHashes)
	{
		$this->writeHashes = $writeHashes;
	}

	protected function prepareFilesToHash()
	{
		$rootPath = $this->rootPath;

		$files = File::getRecursiveDirectoryIterator($rootPath);
		foreach ($files AS $file)
		{
			if ($file->isDir())
			{
				continue;
			}

			$fileName = $file->getFilename();

			// skip hidden dot files, e.g. .DS_Store, .gitignore etc.
			if ($fileName[0] == '.' && $fileName != '.htaccess')
			{
				continue;
			}

			// don't hash the hashes file if it already exists
			if ($fileName == 'hashes.json')
			{
				continue;
			}

			$this->filesToHash[] = $file->getPathname();
		}

		$this->filesPrepared = true;
	}

	/**
	 * Generates the hashes for the given root path. Optionally writes to the specified path (if provided).
	 *
	 * @return array|string The generated hashes JSON
	 *
	 * @throws \ErrorException
	 */
	public function generate()
	{
		if (!$this->filesPrepared)
		{
			$this->prepareFilesToHash();
		}

		$output = [];

		foreach ($this->filesToHash AS $path)
		{
			if (!file_exists($path))
			{
				continue;
			}

			$path = $this->standardizeSeparator($path);
			$root = $this->standardizeSeparator($this->rootPath);

			$key = preg_replace('#^' . $root . '/#', '', $path, 1);
			$output[$key] = Hash::hashTextFile($path, 'sha256');
		}

		ksort($output, SORT_NATURAL | SORT_FLAG_CASE);

		$output = Json::jsonEncodePretty($output);

		if ($this->writeHashes)
		{
			if (!$this->hashesPath)
			{
				throw new \InvalidArgumentException('Trying to write hashes file, but no hashes path provided.');
			}

			$written = File::writeFile($this->hashesPath, $output, false);
			if (!$written)
			{
				throw new \ErrorException('Unexpected failure while writing hashes to provided path.');
			}
		}

		return $output;
	}

	protected function standardizeSeparator($path)
	{
		return str_replace('\\', '/', $path);
	}
}
