<?php namespace Intellex\Upload;

/**
 * The main handler for uploads.
 */
class Handler {

	/** @var string A crypted reference to a file. */
	private $token;

	/** @var UploadFile The temporal file. */
	private $file;

	/** @var array The additional data to send in the response. */
	private $meta;

	/**
	 * Handle the incoming file.
	 *
	 * @param UploadFile $file        The received file.
	 * @param string     $destination The desired temporal destination of file.
	 * @param string     $key         A secret key to use for encryption.
	 */
	public function __construct($file, $destination, $key) {

		// Validate path
		Assert::orThrow(is_dir($destination), '\InvalidArgumentException', "Supplied path `{$destination}` is not a directory.");
		Assert::orThrow(is_writable($destination), '\InvalidArgumentException', "Supplied path `{$destination}` is not writable.");

		// Generate token
		do {
			$basename = hash('md5', time() * 10931) . '.' . $file->getExtension(true);
			$path = $destination . DIRECTORY_SEPARATOR . $basename;

		} while (is_file($path));

		// Move to temporal directory
		$file->moveTo($path);
		$this->file = $file;
		$this->token = static::encryptFilename($basename, $key);
	}

	/**
	 * Set the additional data to be sent in the response.
	 *
	 * @param array $meta The additional data to send in the response.
	 */
	public function setMeta($meta) {
		$this->meta = $meta;
	}

	/**
	 * Send a success response.
	 */
	public function respond() {
		static::send(null, $this->getToken(), $this->getFile(), $this->getMeta());
	}

	/**
	 * Get a file from supplied token.
	 *
	 * @param string $token    The token received from receive() method.
	 * @param string $temporal The temporal location of the file.
	 * @param string $key      A secret key to use for decryption.
	 *
	 * @return UploadFile The requested file.
	 */
	public static function loadFile($token, $temporal, $key) {
		return new UploadFile($temporal . DIRECTORY_SEPARATOR . str_replace('..', '', static::decryptFilename($token, $key)));
	}

	/**
	 * Store the file to its final position.
	 *
	 * @param string $token       The token received from receive() method.
	 * @param string $temporal    The temporal location of the file.
	 * @param string $destination The desired final destination of file.
	 * @param string $key         A secret key to use for decrypting.
	 *
	 * @return UploadFile The saved file.
	 */
	public static function save($token, $temporal, $destination, $key) {
		$file = static::loadFile($token, $temporal, $key);
		$file->moveTo($destination);

		return $file;
	}

	/**
	 * Crypt to a filesystem-friendly format.
	 *
	 * @param string $input The original filename.
	 * @param string $key   A secret key to use for encrypting.
	 *
	 * @return string A filesystem-friendly crypted filename.
	 */
	public static function encryptFilename($input, $key) {
		return Coder::encode(Crypto::encrypt($input, $key));
	}

	/**
	 * Crypt to a filename.
	 *
	 * @param string $input The crypted filename.
	 * @param string $key   A secret key to use for decrypting.
	 *
	 * @return string An original filename.
	 */
	public static function decryptFilename($input, $key) {
		return Crypto::decrypt(Coder::decode($input), $key);
	}

	/** @return string A crypted reference to a file. */
	public function getToken() {
		return $this->token;
	}

	/** @return UploadFile The final file. */
	public function getFile() {
		return $this->file;
	}

	/** @return array The additional data to send in the response. */
	public function getMeta() {
		return $this->meta;
	}

	/**
	 * Register that an error occurred, so send the error as a response.
	 *
	 * @param \Exception $exception The exception that raised an error.
	 */
	public static function error(\Exception $exception) {
		static::send($exception);
	}

	/**
	 * Send a proper JSON response to the client.
	 *
	 * @param \Exception|null $error The exception raised, or null if all went just file.
	 * @param string|null     $token A crypted reference to a file.
	 * @param UploadFile|null $file  The file which is created.
	 * @param mixed|null      $meta  The additional data.
	 */
	public static function send($error, $token = null, $file = null, $meta = null) {

		// Prepare JSON
		$json = json_encode([
			'success' => $error === null,
			'token'   => $token,
			'error'   => $error ? $error->getMessage() : null,
			'file'    => $file ? $file->details() : null,
			'meta'    => $meta
		]);

		// Send
		header('Content-Type: application/json');
		header('Content-Length: ' . strlen($json));
		echo $json;
		exit(0);
	}

}
