<?php
/**
 * @author Thomas Müller <thomas.mueller@tmit.eu>
 *
 * @copyright Copyright (c) 2017, ownCloud GmbH
 * @license AGPL-3.0
 *
 * This code is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * 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, version 3,
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

namespace OCA\DAV\Files;

use OCA\DAV\Connector\Sabre\Exception\FileLocked;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Encryption\Exceptions\GenericEncryptionException;
use OCP\Files\ForbiddenException;
use OCP\Files\IPreviewNode;
use OCP\Files\StorageNotAvailableException;
use OCP\IPreview;
use OCP\Lock\LockedException;
use Sabre\DAV\Exception;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\Exception\ServiceUnavailable;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;

class PreviewPlugin extends ServerPlugin {
	/** @var Server */
	protected $server;
	/** @var ITimeFactory */
	private $timeFactory;
	/** @var IPreview */
	private $previewManager;

	/**
	 * PreviewPlugin constructor.
	 *
	 * @param ITimeFactory $timeFactory
	 * @param IPreview $previewManager
	 */
	public function __construct(ITimeFactory $timeFactory, IPreview $previewManager) {
		$this->timeFactory = $timeFactory;
		$this->previewManager = $previewManager;
	}

	/**
	 * Initializes the plugin and registers event handlers
	 *
	 * @param Server $server
	 * @return void
	 */
	public function initialize(Server $server) {
		$this->server = $server;
		$this->server->on('method:GET', [$this, 'httpGet'], 90);
	}

	/**
	 * Intercepts GET requests on node urls ending with ?preview.
	 * The node has to implement IPreviewNode
	 *
	 * @param RequestInterface $request
	 * @param ResponseInterface $response
	 * @return bool
	 * @throws NotFound
	 * @throws \Sabre\DAVACL\Exception\NeedPrivileges
	 * @throws \Sabre\DAV\Exception\NotAuthenticated
	 * @throws Forbidden
	 * @throws FileLocked
	 * @throws ServiceUnavailable
	 */
	public function httpGet(RequestInterface $request, ResponseInterface $response) {
		$queryParams = $request->getQueryParameters();
		if (!\array_key_exists('preview', $queryParams)) {
			return true;
		}

		$path = $request->getPath();
		$node = $this->server->tree->getNodeForPath($path);

		if (!$node instanceof IFileNode) {
			throw new BadRequest('Unsupported file type');
		}
		$fileNode = $node->getNode();
		if (!$fileNode instanceof IPreviewNode) {
			throw new BadRequest('Unsupported file type');
		}

		// Checking ACL, if available.
		if ($aclPlugin = $this->server->getPlugin('acl')) {
			/** @var \Sabre\DAVACL\Plugin $aclPlugin */
			'@phan-var \Sabre\DAVACL\Plugin $aclPlugin';
			$aclPlugin->checkPrivileges($path, '{DAV:}read');
		}

		try {
			if (!$this->previewManager->isAvailable($fileNode)) {
				// no preview available or preview disabled
				throw new NotFound();
			}
			if ($image = $fileNode->getThumbnail($queryParams)) {
				if ($image === null || !$image->valid()) {
					throw new NotFound();
				}
				$type = $image->mimeType();
				if (!\in_array($type, ['image/png', 'image/jpeg', 'image/gif'])) {
					$type = 'application/octet-stream';
				}

				// Enable output buffering
				\ob_start();
				// Capture the output
				$image->show();
				$imageData = \ob_get_contents();
				// Clear the output buffer
				\ob_end_clean();

				$response->setHeader('Content-Type', $type);
				$response->setHeader('Content-Disposition', 'attachment');
				// cache 24h
				$response->setHeader('Cache-Control', 'max-age=86400, must-revalidate');
				$response->setHeader('Expires', \gmdate('D, d M Y H:i:s', $this->timeFactory->getTime() + 86400) . ' GMT');

				$response->setStatus(200);
				$response->setBody($imageData);

				// Returning false to break the event chain
				return false;
			}
		} catch (GenericEncryptionException $ex) {
			// returning 403 because some apps stops syncing if 503 is returned.
			throw new Forbidden('Encryption not ready: ' . $ex->getMessage());
		} catch (StorageNotAvailableException $ex) {
			throw new ServiceUnavailable('Failed to open file: ' . $ex->getMessage());
		} catch (ForbiddenException $ex) {
			throw new Forbidden($ex->getMessage(), $ex->getRetry());
		} catch (LockedException $ex) {
			throw new FileLocked($ex->getMessage(), $ex->getCode(), $ex);
		} catch (Exception $ex) {
			throw $ex;
		} catch (\Exception $ex) {
			throw new BadRequest($ex->getMessage(), $ex->getCode(), $ex);
		}

		// TODO: add forceIcon handling .... if still needed
		throw new NotFound();
	}
}
