<?php

namespace StevenBuehner\sbChurchtoolsGrouphomepage\Helper;

use StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\ICal\Event;
use StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\ICal\ICal;
use DateTimeZone;
use DateTime;
use DateInterval;
use Exception;
use StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\Psr\Cache\InvalidArgumentException;
use StevenBuehner\sbChurchtoolsGrouphomepage\Vendor\Symfony\Contracts\Cache\ItemInterface;

class iCalHelper {

	protected ICal         $iCal;
	protected string       $url;
	protected DateTimeZone $targetTimezone;
	protected DateTimeZone $iCalTimeZone;

	/**
	 * iCalHelper constructor.
	 */
	public function __construct(string $url, ?DateTimeZone $iCalTimeZone = NULL, ?DateTimeZone $targetTimeZone = NULL) {
		$this->url = $url;

		if ($iCalTimeZone instanceof DateTimeZone) {
			$this->iCalTimeZone = $iCalTimeZone;
		} else if ($iCalTimeZone === NULL) {
			$this->iCalTimeZone = new DateTimeZone('UTC');
		} else {
			$this->iCalTimeZone = new DateTimeZone($iCalTimeZone);
		}

		if ($targetTimeZone instanceof DateTimeZone) {
			$this->targetTimezone = $targetTimeZone;
		} else if ($targetTimeZone === NULL) {
			// Wordpress-Setting for Timezone
			// https://stackoverflow.com/questions/14068594/is-it-possible-to-create-a-datetimezone-from-an-offset
			// $gmtFormat      = sprintf('%+03d00');
			$this->targetTimezone = new DateTimeZone(wp_timezone_string());
		} else {
			$this->targetTimezone = new DateTimeZone($targetTimeZone);
		}

		$this->iCal = new ICal(FALSE, [
			'filterDaysBefore' => 1,
			'defaultTimeZone'  => $this->iCalTimeZone->getName(),
		]);

		$cacheHelper = CacheHelper::getInstance();
		$cacheKey    = sha1($url);
		$iCal        = $this->iCal;

		$this->iCal = $cacheHelper->get($cacheKey, function (ItemInterface $item) use ($url, $iCal) {
			// $item->expiresAfter(60 * 10);
			$iCal->initUrl($url);
			return $iCal;
		});

	}

	public static function getImageUrl(Event $e): ?string {

		$addProps = $e->additionalProperties;

		if (!empty($addProps['image'])) {
			return $addProps['image'];
		}

		return NULL;

	}

	/**
	 * @param string|null $limitTitle
	 * @param string|null $limitDescription
	 * @param DateTime|null $maxDate
	 * @param int $maxElements
	 * @return Event[]
	 * @throws InvalidArgumentException
	 */
	public function getFilteredEventsCached(?string $limitTitle, ?string $limitDescription, ?DateTime $maxDate = NULL, int $maxElements = 0): array {
		$cacheHelper = CacheHelper::getInstance();
		$cacheKey    = sha1(json_encode([$this->url, $limitTitle, $limitDescription, $maxDate, $maxElements]));

		return $cacheHelper->get($cacheKey, function (ItemInterface $item) use ($limitTitle, $limitDescription, $maxDate, $maxElements) {
			// $item->expiresAfter(60 * 10);
			return $this->getFilteredEvents($limitTitle, $limitDescription, $maxDate, $maxElements);
		});
	}


	/**
	 * @param string|null $limitTitle
	 * @param string|null $limitDescription
	 * @param ?DateTime $maxDate
	 * @param int $maxElements
	 * @return Event[]
	 * @throws Exception
	 */
	public function getFilteredEvents(?string $limitTitle, ?string $limitDescription, ?DateTime $maxDate = NULL, int $maxElements = 0): array {

		$maxElements = max(0, $maxElements);

		// MaxDate
		if ($maxDate instanceof DateTime) {
			$events = $this->iCal->eventsFromRange(NULL, $maxDate->format('d-m-Y'));
		} else {
			$events = $this->iCal->events();
		}

		$events = $this->iCal->sortEventsWithOrder($events);

		// Filter Title (=summary)
		if (!empty($limitTitle)) {
			$pattern = self::getPatternString($limitTitle);
			$events  = array_filter($events, function (Event $e) use ($pattern) {
				return (preg_match($pattern, "$e->summary") === 1);
			});
		}

		// Filter Description
		if (!empty($limitDescription)) {
			$pattern = self::getPatternString($limitDescription);
			$events  = array_filter($events, function (Event $e) use ($pattern) {
				return (preg_match($pattern, "$e->description") === 1);
			});
		}

		if ($maxElements > 0) {
			$events = array_slice($events, 0, $maxElements);
		}


		return $events;

	}

	protected function dateIntervalEqual(DateInterval $di1, DateInterval $di2): bool {
		return $di1->d == $di2->d && $di1->i == $di2->i && $di1->h == $di2->h && $di1->m == $di2->m && $di1->s == $di2->s && $di1->y == $di2->y;
	}


	/**
	 * Ganztägige Ereignisse
	 */
	public static function isAllDayDateRange(DateTime $start, DateTime $end): bool {
		$difference = $start->diff($end);

		$mod  = ((int)$difference->format('h')) % 24;
		$time = $start->format('His');

		return ($mod === 0 && $time == 0);
	}

	public static function sameDay(DateTime $d1, DateTime $d2): bool {

		$diff = $d1->diff($d2);
		// $test = \DateInterval::createFromDateString('1 day');

		return
			($diff->d === 1 && $diff->i === 0 && $diff->h === 0 && $diff->s === 0 && $diff->m === 0 && $diff->y === 0) ||
			($d1->format('Ymd') === $d2->format('Ymd'));

	}


	/**
	 * @param Event $e
	 * @return DateTime|null
	 * @throws Exception
	 */
	public function getStartDateTime(Event $e): ?DateTime {

		if ($e->dtstart === NULL) {
			return NULL;
		}

		$datetime = $this->iCal->iCalDateToDateTime($e->dtstart);

		if ($datetime->getTimezone()->getName() !== $this->iCalTimeZone->getName()) {
			$datetime = $this->overrideTimezone($datetime, $this->iCalTimeZone);
		}

		$datetime->setTimezone($this->targetTimezone);


		return $datetime;
	}

	/**
	 * @param Event $e
	 * @return DateTime|null
	 * @throws Exception
	 */
	public function getEndDateTime(Event $e): ?DateTime {

		if ($e->dtend === NULL) {
			return NULL;
		}

		$datetime = $this->iCal->iCalDateToDateTime($e->dtend);

		if ($datetime->getTimezone()->getName() !== $this->iCalTimeZone->getName()) {
			$datetime = $this->overrideTimezone($datetime, $this->iCalTimeZone);
		}

		$datetime->setTimezone($this->targetTimezone);

		return $datetime;
	}

	protected function overrideTimezone(DateTime $dateTime, DateTimeZone $timeZone): DateTime|bool {
		return DateTime::createFromFormat('Y-m-d H:i:s', $dateTime->format('Y-m-d H:i:s'), $timeZone);
	}


	/**
	 * @param Event $e
	 * @return false|string
	 */
	public static function getUrl(Event $e): bool|string {
		if (isset($e->url)) {
			return $e->url;
		}

		return FALSE;
	}

	public static function getPatternString($string): string {
		return "~$string~";
	}

	public static function isValidRegExp($string): bool {
		return preg_match(self::getPatternString($string), "") !== FALSE;
	}

	public static function isValidUrl($url) {
		if (empty($url)) {
			return FALSE;
		}

		return filter_var($url, FILTER_VALIDATE_URL);
	}


	public static function formatLocalizedDateRange(DateTime $start, DateTime $end, bool $onlyDisplayDateIfNeccessary = TRUE): string {
		$result     = '';
		$dateFormat = get_option('date_format');
		// $wpTimezone = wp_timezone_string(); // https://developer.wordpress.org/reference/functions/wp_timezone_string/
		$isAllDay = self::isAllDayDateRange($start, $end);

		if (self::sameDay($start, $end) === TRUE) {
			// Start und Ende am selben Tag

			if ($onlyDisplayDateIfNeccessary === FALSE) {
				$result = wp_date($dateFormat, $start->getTimestamp());
			}

			if ($isAllDay) {
				// ganztägiges Ereignis (=> keine Uhrzeit angeben)
			} else {

				if (!empty($result)) {
					$result .= ' ';
				}

				$result .= wp_date('H:i', $start->getTimestamp());
				$result .= ' - ';
				$result .= wp_date('H:i', $end->getTimestamp());
				$result .= ' ';
				$result .= translate('Uhr');

			}

		} else {
			// Mehrere Tage

			if ($start->format('Y') === $end->format('Y')) {
				// Gleiches Jahr => kann eingespart werden
				$formatOhneJahr = preg_replace('~\s*[Yy]+\s*~', '', $dateFormat);

				if ($isAllDay && $start->format('m') === $end->format('m')) {
					// Gleicher Monat => kann eingespart werden
					$formatOhneJahrUndMonat = preg_replace('~\s*[mMF]+\s*~', '', $formatOhneJahr);
					$result                 .= wp_date($formatOhneJahrUndMonat, $start->getTimestamp());

				} else {
					$result .= wp_date($formatOhneJahr, $start->getTimestamp());
				}

				if (!$isAllDay) {
					$result .= ' ' . wp_date('H:i', $start->getTimestamp()) . ' ' . translate('Uhr');
				}
			}

			$result .= ' - ';

			$result .= wp_date($dateFormat, $end->getTimestamp());

			if (!$isAllDay) {
				$result .= ' ' . wp_date('H:i', $end->getTimestamp()) . ' ' . translate('Uhr');
			}

		}

		return $result;
	}

	public function sortEvents($events): array {
		return $this->iCal->sortEventsWithOrder($events);
	}

	public function getCalendarName(): string {
		return $this->iCal->calendarName();
	}

	public function getCalendarDescription(): string {
		return $this->iCal->calendarDescription();
	}

	public function getCalendarTimezone(): ?string {
		return $this->iCal->calendarTimeZone();
	}

}
