<?php
namespace OCA\Memento\Controller;

require_once __DIR__ . '/datetimeConversion.php';
require_once __DIR__ . '/getUrlParameter.php';

use \Exception;
use OCP\IRequest;
use OCP\IServerContainer;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\DataDisplayResponse;

class TimeGateController extends Controller {
	use MementoFinder;

	private $loggedInUserId;
	private $serverContainer;

	public function __construct(
		$AppName,
		IRequest $request,
		$UserId,
		IServerContainer $serverContainer
	) {
		parent::__construct($AppName, $request);
		$this->loggedInUserId = $UserId;
		$this->serverContainer = $serverContainer;
	}

	/**
	 * @NoAdminRequired
	 * @NoCSRFRequired
	 * @PublicPage
	 */
	public function singleUserTimeGate($userId, $url) {
		// XXX workaround, as nextcloud corrupts the $url parameter.
		$url = getUrlParameter("u/$userId/timegate");

		$matchingMementos = $this->findSingleUserMementosForUrl($userId, $url);

		return $this->makeResponse($url, $matchingMementos);
	}

	/**
	 * @NoAdminRequired
	 * @NoCSRFRequired
	 * @PublicPage
	 */
	public function allUsersTimeGate($url) {
		$url = getUrlParameter('timegate');

		$matchingMementos = $this->findAllUsersMementosForUrl($url);

		return $this->makeResponse($url, $matchingMementos);
	}

	private function makeResponse($url, $matchingMementos) {
		// Choose one of the matched mementos, if any.
		if (count($matchingMementos) === 0) {
			// No matches. :(
			$message = "<h1>No snapshots found for requested URL. :(</h1>";
			return new DataDisplayResponse($message, 404);
		} else if (count($matchingMementos) === 1) {
			// One match; no need to choose.
			$chosenMemento = $matchingMementos[0];
		} else {
			// Multiple matches: choose based on requested date.
			$acceptDatetimeHeader = $this->request->getHeader('Accept-Datetime');
			if ($acceptDatetimeHeader) {
				try {
					$requestedDatetime = datetimeStringToTimestamp($acceptDatetimeHeader);
				} catch (Exception $e) {
					return new DataDisplayResponse("Invalid Accept-Datetime header.", 400);
				}
			} else {
				// Not sending the header means requesting the most recent version.
				$requestedDatetime = time();
			}
			// Pick the one closest to the requested date (either before or after it).
			$chosenMemento = minBy($matchingMementos,
				function ($matchingMemento) use ($requestedDatetime) {
					return abs($matchingMemento['datetime'] - $requestedDatetime);
				}
			);
		}

		// Send a 302 Found redirect pointing to the chosen memento.
		$response = new RedirectResponse($chosenMemento['mementoUrl']);
		$response->setStatus(302);
		// Both the requested datetime and the authenticated user influence the response.
		$response->addHeader('Vary', 'accept-datetime, cookie');

		// Add a link to the original(s) and to the timemap.
		$originalLinks = implode(", ", array_map(
			function ($originalUrl) { return "<$originalUrl>;rel=\"original\""; },
			$chosenMemento['originalUrls']
		));
		// XXX hardcoding the route URL.
		$timeMapUrl = $this->serverContainer->getURLGenerator()
			->getAbsoluteUrl("/apps/memento/timemap/$url");
		$firstDatetime = datetimeTimestampToString($matchingMementos[0]['datetime']);
		$lastMemento = $matchingMementos[count($matchingMementos)-1];
		$lastDatetime = datetimeTimestampToString($lastMemento['datetime']);
		$timeMapLink = "<$timeMapUrl>"
			. ";rel=\"timemap\""
			. ";type=\"application/link-format\""
			. ";from=\"$firstDatetime\";until=\"$lastDatetime\"";
		$response->addHeader('Link', "$originalLinks, $timeMapLink");

		return $response;
	}
}

function minBy($array, $iteratee) {
	$values = array_map($iteratee, $array);
	$argmin = array_search(min($values), $values);
	return $array[$argmin];
}