<?php

namespace FluentBooking\Package\CalDav\ICal;

use Exception;
use DateTime;
use DateTimeZone;

class Event implements \JsonSerializable
{
	protected $data = [];
	
	protected $generatedEventData = '';

	protected $dateTimeFormat = 'Ymd\THis\Z';

	public function __construct($array = [])
	{
		$this->data = $array;

		$this->setDefaults();
	}

	public function preCompile()
	{
		return $this->prepareEventData();
	}

	protected function prepareEventData()
	{
		$this->setDateTime();
		$this->setCategories();
		$this->setAttendees();
		$this->setOrganizer();
		$this->setAlarm();
		$this->setRecurrence();
		$this->setAttachments();
	}

	protected function setDefaults()
	{
		if (!isset($this->data['uid'])) {
			$currentDateTime = $this->getCurrentDateTime();
			$this->data['created'] = $currentDateTime;
			$this->data['dtstamp'] = $currentDateTime;
			$this->data['last-modified'] = $currentDateTime;
			$this->data['uid'] = md5(__NAMESPACE__) . '-' . wp_generate_uuid4();
		}
	}

	protected function getCurrentDateTime()
	{
		return (
			new DateTime('now', new DateTimeZone('UTC'))
		)->format($this->dateTimeFormat);
	}

	protected function setDateTime()
	{
		$dates = [
			'dtstart' => $this->getDtStart(),
			'dtend' => $this->getDtEnd()
		];

		$timezone = !empty($this->data['timezone']) && $this->data['timezone'] !== 'UTC'
			? $this->data['timezone']
			: null;

		$prefix = $timezone ? ';TZID=' . $timezone . ':' : ':';
		$format = $timezone ? 'Ymd\THis' : $this->dateTimeFormat;

		foreach ($dates as $key => $value) {

			if ($this->isPrefixedDateTime($value)) {
				continue;
			}

			$dateTime = is_string($value)
				? new DateTime($value, new DateTimeZone('UTC'))
				: $value;

			if (!$dateTime instanceof DateTime) {
				throw new Exception("Invalid value: {$value} for {$key}.");
			}

			if ($timezone) {
				$dateTime->setTimezone(new DateTimeZone($timezone));
			}

			$this->data[$key] = $prefix . $dateTime->format($format);
		}
	}

	private function isPrefixedDateTime($value)
	{
		return is_string($value) && isset($value[0]) && ($value[0] === ':' || $value[0] === ';');
	}

	protected function getDtStart()
	{
		if (!isset($this->data['dtstart'])) {
			if (!isset($this->data['DTSTART'])) {
				throw new Exception('The Event needs an dtstart/DTSTART property.');
			} else {
				$dtStart = $this->data['DTSTART'];
			}
		} else {
			$dtStart = $this->data['dtstart'];
		}

		return $dtStart;
	}

	protected function getDtEnd()
	{
		if (!isset($this->data['dtend'])) {
			if (!isset($this->data['DTEND'])) {
				throw new Exception('The Event needs an dtend/DTEND property.');
			} else {
				$dtEnd = $this->data['DTEND'];
			}
		} else {
			$dtEnd = $this->data['dtend'];
		}

		return $dtEnd;
	}

	protected function setCategories()
	{
		if (!isset($this->data['categories'])) {
			return;
		}

		$categories = $this->data['categories'];

		if (is_array($categories)) {
			$categories = implode(',', $categories);
		}

		$categories = str_replace(', ', ',', $categories);
		
		$this->data['categories'] = $categories;
	}

	protected function setAttendees()
	{
		if (!isset($this->data['attendees'])) {
			return;
		}

		foreach ($this->data['attendees'] as &$attendee) {
			
			if (!isset($attendee['email'])) {
				throw new Exception('An attendee must have an email.');
			}

			if (!isset($attendee['name'])) {
				$attendee['name'] = $attendee['email'];
			}

			$str = '';
			
			foreach ($attendee as $key => $value) {	
				$key = strtolower($key);

				if ($key == 'name') {
					$str .= "CN={$value};";
				} elseif ($key == 'role') {
					$value = strtoupper($value);
					$str .= "ROLE={$value};";
				}  elseif ($key == 'rsvp') {
					if (is_bool($value)) {
						$value = $value === true ? 'TRUE' : 'FALSE';
					}
					$value = strtoupper($value);
					$str .= "RSVP={$value};";
				} elseif ($key == 'partstat') {
                    $value = strtoupper($value);
                    $str .= "PARTSTAT={$value};";
                }
			}

			$str = rtrim($str, ';');

			$str .= ';SCHEDULE-AGENT=CLIENT';

			$str .= ":mailto:{$attendee['email']}";

			$attendee['str'] = $str;
		}
	}

	protected function setOrganizer()
	{
		if (!isset($this->data['organizer'])) {
			return;
		}

		$organizer = &$this->data['organizer'];

		if (!isset($organizer['email'])) {
			throw new \Exception('An organizer must have an email.');
		}

		if (!isset($organizer['name'])) {
			$organizer['name'] = $organizer['email'];
		}

		$str = '';

		foreach ($organizer as $key => $value) {
			
			$key = strtolower($key);

			if ($key == 'name') {
				$str .= "CN={$value};";
			} elseif ($key == 'language') {
				$str .= "LANGUAGE={$value};";
			}
		}

		if (isset($organizer['sent_by'])) {
			$str .= "SENT-BY=mailto:{$organizer['sent_by']}";
		}

		$str = rtrim($str, ';');

		$str .= ';SCHEDULE-AGENT=CLIENT';

		$str .= ":mailto:{$organizer['email']}";

		$organizer['str'] = $str;
	}

	protected function setAlarm()
	{
		if (!isset($this->data['alarm'])) {
			return;
		}

		$alarm = &$this->data['alarm'];

		$action = strtoupper($alarm['action']);

		$alarm['action'] = $action;

		if (isset($alarm['trigger'])) {
			
			// For exact time of the event
			if ($alarm['trigger'] === true) {
				$alarm['trigger_str'] = "RELATED=START:PT0S";

				return;
			}

			if (!isset($alarm['trigger']['after'])) {

				if (isset($alarm['trigger']['before'])) {
					$isBefore = $alarm['trigger']['before'] === true;
				} else {
					$isBefore = true;
				}
			} else {
				$isBefore = $alarm['trigger']['after'] === false;
			}

			$period = $isBefore ? '-P' : 'P';
			
			$time = '';

			foreach ($alarm['trigger'] as $key => $value) {
				if ($key == 'years' && is_numeric($value) && $value) {
					$period = $period . $value . 'Y';
				} elseif ($key == 'months' && is_numeric($value) && $value) {
					$period = $period . $value . 'M';
				} elseif ($key == 'days' && is_numeric($value) && $value) {
					$period = $period . $value . 'D';
				} elseif ($key == 'hours' && is_numeric($value) && $value) {
					$time = $time . $value . 'H';
				} elseif ($key == 'minutes' && is_numeric($value) && $value) {
					$time = $time . $value . 'M';
				} elseif ($key == 'seconds' && is_numeric($value) && $value) {
					$time = $time . $value . 'S';
				}
			}
		}

		if ($time) {
			$period = $period . 'T' . $time;
		}
		
		$alarm['trigger_str'] = "RELATED=START:{$period}";
	}

	protected function setRecurrence()
	{
		if (!isset($this->data['repeat'])) {
			return;
		}
		
		$str = '';
		
		$recurrence = &$this->data['repeat'];

		foreach ($recurrence as $key => $value) {

			$value = is_array($value) ? implode(',', $value) : $value;

			$str .= strtoupper($key) . '=' . strtoupper($value) . ';';
		}

		$str = rtrim($str, ';');

		$recurrence['str'] = $str;
	}

	public function setAttachments()
	{
		if (!isset($this->data['attachments'])) {
			return;
		}

		$attachments = &$this->data['attachments'];

		foreach ($this->data['attachments'] as $attachment) {

			if (!isset($attachment['url'], $attachment['name'])) {
				throw new Exception('An attachment requires a name and url key.');
			}

			$url = $attachment['url'];
			
			$filename = $attachment['name'];

			$attachments['list'][] = 'ATTACH;FILENAME=' . $filename . ':' . $url;
		}
	}

	public function compile()
	{
		$this->preCompile();

		if (!isset($this->data['sequence'])) {
			$this->data['sequence'] = 0;
		} else {
			$this->data['sequence'] += 1;
		}
		
		$this->data['last-modified'] = $this->getCurrentDateTime();

		$appNS = strtolower(explode('\\', __NAMESPACE__)[0]);
		$calendar[] = "BEGIN:VCALENDAR";
		$calendar[] = "VERSION:2.0";
		$calendar[] = "CALSCALE:GREGORIAN";
		$calendar[] = "PRODID:-//authlab.{$appNS}//CalDAV Client//EN";
		$calendar[] = "BEGIN:VEVENT";
		$calendar[] = "CREATED:{$this->data['created']}";
		$calendar[] = "DTSTAMP:{$this->data['dtstamp']}";
		$calendar[] = "LAST-MODIFIED:{$this->data['last-modified']}";
		$calendar[] = "SEQUENCE:{$this->data['sequence']}";
		$calendar[] = "UID:{$this->data['uid']}";
		$calendar[] = "DTSTART{$this->data['dtstart']}";
		$calendar[] = "DTEND{$this->data['dtend']}";
		
		if (isset($this->data['transp'])) {
			$calendar[] = "TRANSP:{$this->data['transp']}";
		} else {
			$calendar[] = "TRANSP:TRANSPARENT";
		}

		if (isset($this->data['status'])) {
			$eventStatus = strtoupper($this->data['status']);
			$calendar[] = "STATUS:{$eventStatus}";
		}

		if (isset($this->data['summary'])) {
			$calendar[] = "SUMMARY:{$this->data['summary']}";
		}

		if (isset($this->data['description'])) {
			$eventDescription = str_replace("\n", " ", $this->data['description']);
			$calendar[] = "DESCRIPTION:{$eventDescription}";
		}

		if (isset($this->data['location'])) {
			$calendar[] = "LOCATION:{$this->data['location']}";
		}

		if (isset($this->data['categories'])) {
			$calendar[] = "CATEGORIES:{$this->data['categories']}";
		}

		if (isset($this->data['repeat'])) {
			$calendar[] = "RRULE:{$this->data['repeat']['str']}";
		}

		if (isset($this->data['attendees'])) {
			foreach ($this->data['attendees'] as $attendee) {
				$calendar[] = "ATTENDEE;{$attendee['str']}";
			}
		}

		if (isset($this->data['organizer'])) {
			$calendar[] = "ORGANIZER;{$this->data['organizer']['str']}";
		}

		if (isset($this->data['attachments'])) {
			$calendar[] = implode("\n", $this->data['attachments']['list']);
		}

		if (isset($this->data['alarm'])) {
			$calendar[] = "BEGIN:VALARM";
			
			if (isset($this->data['alarm']['action'])) {
				$calendar[] = "ACTION:{$this->data['alarm']['action']}";
			}

			if (isset($this->data['alarm']['description'])) {

				$alarmDescription = str_replace(
					"\n", " ", $this->data['alarm']['description']
				);
				
				$calendar[] = "DESCRIPTION:{$alarmDescription}";
			}

			if (isset($this->data['alarm']['trigger'])) {
				$calendar[] = "TRIGGER;{$this->data['alarm']['trigger_str']}";
			}

			$calendar[] = "END:VALARM";
		}

		$calendar[] = "END:VEVENT";
		$calendar[] = "END:VCALENDAR";

		$this->generatedEventData = implode("\n", $calendar);

		return $this->generatedEventData;
	}

	public function getUid()
	{
		return $this->data['uid'];
	}

	public function __get($key)
	{
		return $this->data[$key];
	}

	public function __set($key, $value)
	{
		$this->data[$key] = $value;
	}

	#[\ReturnTypeWillChange]
	public function jsonSerialize()
	{
		return $this->data;
	}
}
