<?php
/**
 * Default Calendar - Grid View
 *
 * @package SimpleCalendar/Calendars
 */
namespace SimpleCalendar\Calendars\Views;

use SimpleCalendar\plugin_deps\Carbon\Carbon;
use SimpleCalendar\plugin_deps\Mexitek\PHPColors\Color;
use SimpleCalendar\Abstracts\Calendar;
use SimpleCalendar\Abstracts\Calendar_View;
use SimpleCalendar\Events\Event;
use SimpleCalendar\Calendars\Default_Calendar;
use \DateTime;
use \DateTimeZone;

if (!defined('ABSPATH')) {
	exit();
}

/**
 * Default Calendar: Grid View.
 *
 * @since  3.0.0
 */
class Default_Calendar_Grid implements Calendar_View
{
	/**
	 * Calendar.
	 *
	 * @access public
	 * @var Default_Calendar
	 */
	public $calendar = null;

	/**
	 * Current display start.
	 *
	 * @access private
	 * @var int
	 */
	private $start = 0;

	/**
	 * Current display end.
	 *
	 * @access private
	 * @var int
	 */
	private $end = 0;

	/**
	 * Constructor.
	 *
	 * @since 3.0.0
	 *
	 * @param string|Calendar $calendar
	 */
	public function __construct($calendar = '')
	{
		$this->calendar = $calendar;
	}

	/**
	 * Get the view parent calendar type.
	 *
	 * @since  3.0.0
	 *
	 * @return string
	 */
	public function get_parent()
	{
		return 'default-calendar';
	}

	/**
	 * Get the view type.
	 *
	 * @since  3.0.0
	 *
	 * @return string
	 */
	public function get_type()
	{
		return 'grid';
	}

	/**
	 * Get the view name.
	 *
	 * @since  3.0.0
	 *
	 * @return string
	 */
	public function get_name()
	{
		return __('Grid', 'google-calendar-events');
	}

	/**
	 * Add ajax actions.
	 *
	 * @since 3.0.0
	 */
	public function add_ajax_actions()
	{
		add_action('wp_ajax_simcal_default_calendar_draw_grid', [$this, 'draw_grid_ajax']);
		add_action('wp_ajax_nopriv_simcal_default_calendar_draw_grid', [$this, 'draw_grid_ajax']);
	}

	/**
	 * Default calendar grid scripts.
	 *
	 * Scripts to load when this view is displayed.
	 *
	 * @since  3.0.0
	 *
	 * @return array
	 */
	public function scripts()
	{
		return [
			'simcal-qtip' => [
				'src' => SIMPLE_CALENDAR_ASSETS . 'generated/vendor/jquery.qtip.min.js',
				'deps' => ['jquery'],
				'in_footer' => true,
			],
			'simcal-default-calendar' => [
				'src' => SIMPLE_CALENDAR_ASSETS . 'generated/default-calendar.min.js',
				'deps' => ['jquery', 'simcal-qtip'],
				'in_footer' => true,
				'localize' => [
					'simcal_default_calendar' => simcal_common_scripts_variables(),
				],
			],
		];
	}

	/**
	 * Default calendar grid styles.
	 *
	 * Stylesheets to load when this view is displayed.
	 *
	 * @since  3.0.0
	 *
	 * @return array
	 */
	public function styles()
	{
		return [
			'simcal-qtip' => [
				'src' => SIMPLE_CALENDAR_ASSETS . 'generated/vendor/jquery.qtip.min.css',
				'media' => 'all',
			],
			'simcal-default-calendar-grid' => [
				'src' => SIMPLE_CALENDAR_ASSETS . 'generated/default-calendar-grid.min.css',
				'deps' => ['simcal-qtip'],
				'media' => 'all',
			],
			'simcal-default-calendar-list' => [
				'src' => SIMPLE_CALENDAR_ASSETS . 'generated/default-calendar-list.min.css',
				'deps' => ['simcal-qtip'],
				'media' => 'all',
			],
		];
	}

	/**
	 * Format the timestamp with timeZone to display exact month as per the diffrent timezone.
	 *
	 * @since  3.4.3
	 */
	public function format_timestamp($timestamp, $format = 'F')
	{
		$datetime = new \DateTime();
		$datetime->setTimezone(new \DateTimeZone($this->calendar->timezone));
		$datetime->setTimestamp($timestamp);
		$formatted_date = $datetime->format($format);

		return $formatted_date;
	}
	/**
	 * Default calendar grid markup.
	 *
	 * @since  3.0.0
	 */
	public function html()
	{
		$calendar = $this->calendar;

		if ($calendar instanceof Default_Calendar) { ?>

			<?php edit_post_link(
   	__('Edit Calendar', 'google-calendar-events'),
   	'<p class="simcal-align-right"><small>',
   	'</small></p>',
   	$calendar->id
   ); ?>

            <table class="simcal-calendar-grid"
                   data-event-bubble-trigger="<?php echo $calendar->event_bubble_trigger; ?>">
                <thead class="simcal-calendar-head">
                <tr>
					<?php if (!$calendar->static) { ?>
                        <th class="simcal-nav simcal-prev-wrapper" colspan="<?php echo apply_filters(
                        	'simcal_prev_cols',
                        	'1'
                        ); ?>">
                            <button class="simcal-nav-button simcal-month-nav simcal-prev" title="<?php _e(
                            	'Previous Month',
                            	'google-calendar-events'
                            ); ?>" aria-label="<?php _e(
	'Previous Month',
	'google-calendar-events'
); ?>"><i class="simcal-icon-left"></i></button>
                        </th>
					<?php } ?>
                    <th colspan="<?php echo apply_filters('simcal_current_cols', $calendar->static ? '7' : '5'); ?>"
                        class="simcal-nav simcal-current"
                        data-calendar-current="<?php echo $calendar->start; ?>">
						<?php
      echo '<h3>';

      // Display month and year according to user date format preference.

      $year_pos = strcspn($calendar->date_format, 'Y y');
      $month_pos = strcspn($calendar->date_format, 'F M m n');

      $current = ['month' => 'F', 'year' => 'Y'];

      if ($year_pos < $month_pos) {
      	$current = array_reverse($current);
      }

      foreach ($current as $k => $v) {
      	echo ' <span class="simcal-current-' . $k, '">' . wp_date($v, $calendar->start) . '</span> ';
      }

      echo '</h3>';
      ?>
                    </th>
					<?php if (!$calendar->static) { ?>
                        <th class="simcal-nav simcal-next-wrapper" colspan="<?php echo apply_filters(
                        	'simcal_next_cols',
                        	'1'
                        ); ?>">
                            <button class="simcal-nav-button simcal-month-nav simcal-next" title="<?php _e(
                            	'Next Month',
                            	'google-calendar-events'
                            ); ?>" aria-label="<?php _e(
	'Next Month',
	'google-calendar-events'
); ?>"><i class="simcal-icon-right"></i></button>
                        </th>
					<?php } ?>
                </tr>
                <tr>
					<?php
     // Print day names in short or long form for different viewport sizes.

     $week_starts = $calendar->week_starts;
     $week_days_short = simcal_get_calendar_names_i18n('day', 'short');
     $week_days_full = simcal_get_calendar_names_i18n('day', 'full');

     for ($i = $week_starts; $i <= 6; $i++): ?>
                        <th class="simcal-week-day simcal-week-day-<?php echo $i; ?>"
                            data-screen-small="<?php echo mb_substr($week_days_short[$i], 0, 1, 'UTF-8'); ?>"
                            data-screen-medium="<?php echo $week_days_short[$i]; ?>"
                            data-screen-large="<?php echo $week_days_full[$i]; ?>"><?php echo $week_days_short[
	$i
]; ?></th>
						<?php endfor;

     if ($week_starts !== 0):
     	for ($i = 0; $i < $week_starts; $i++): ?>
                            <th class="simcal-week-day simcal-week-day-<?php echo $i; ?>"
                                data-screen-small="<?php echo mb_substr($week_days_short[$i], 0, 1, 'UTF-8'); ?>"
                                data-screen-medium="<?php echo $week_days_short[$i]; ?>"
                                data-screen-large="<?php echo $week_days_full[$i]; ?>"><?php echo $week_days_short[
	$i
]; ?></th>
							<?php endfor;
     endif;
     ?>
                </tr>
                </thead>

				<?php echo $this->draw_month(
    	date($this->format_timestamp($calendar->start, 'n')),
    	date($this->format_timestamp($calendar->start, 'Y'))
    ); ?>	 			
            </table>

			<?php
   $is_print_calendar = get_post_meta($calendar->id, '_display_print_calendar');
   if (isset($is_print_calendar[0]) && !empty($is_print_calendar[0]) && $is_print_calendar[0] === 'yes') {
   	echo '<button id="print-calendar-button" class="print-calendar-button demo_sc_primary_btn">Print Calendar</button>';
   }
   }
	}

	/**
	 * Added sorting for events with the same start time to be sorted
	 * alphabetically.
	 *
	 * @since  3.1.28
	 * @access private
	 */
	private static function cmp($a, $b)
	{
		if ($a->start == $b->start) {
			if ($a->title == $b->title) {
				return 0;
			}
			return $a->title < $b->title ? -1 : 1;
		} else {
			return $a->start < $b->start ? -1 : 1;
		}
	}

	/**
	 * Make a calendar grid.
	 *
	 * Outputs an html calendar according to month and year passed in arguments.
	 * Loosely inspired by: http://davidwalsh.name/php-calendar
	 * Adjusted by timezone and with an arbitrary week start day.
	 *
	 * @since  3.0.0
	 * @access private
	 *
	 * @param  int $month The month to print (two digits).
	 * @param  int $year  The corresponding year (four digits).
	 * @param  int $id    The calendar id.
	 *
	 * @return string
	 */
	private function draw_month($month, $year, $id = 0)
	{
		$calendar = $this->calendar;
		if (empty($calendar)) {
			$calendar = simcal_get_calendar(intval($id));
			if (!$calendar) {
				return '';
			}
		}

		$events = $calendar->events;

		// Variables to cycle days in current month and find today in calendar.
		$now = $calendar->now;
		$current = Carbon::create($year, $month, 1, 0, 0, 0, $calendar->timezone);
		$current_min = $current->getTimestamp();
		$current_max = $current->endOfDay()->getTimestamp();

		// Calendar grid variables.
		$week_starts = $calendar->week_starts;
		$week_of_year = $current->weekOfYear; // Relative count of the week number of the year.
		$month_starts = $current->dayOfWeek; // Day upon which the month starts.
		$days_in_month = $current->daysInMonth; // Number of days in the given month.

		// Set current month events timestamp boundaries.
		$this->start = $current_min;
		$this->end = $current->endOfMonth()->getTimestamp();

		// Get daily events for this month.
		if ($events && is_array($events)) {
			// Filter events within the boundaries previously set above.
			$timestamps = array_keys($events);
			$lower_bound = array_filter($timestamps, [$this, 'filter_events_before']);
			$higher_bound = array_filter($lower_bound, [$this, 'filter_events_after']);

			$current_month = Carbon::createFromTimestamp($this->start, $calendar->timezone)->month;
			$current_year = Carbon::createFromTimestamp($this->start, $calendar->timezone)->year;

			$filtered =
				is_array($events) && is_array($higher_bound) && !empty($events) && !empty($higher_bound)
					? array_intersect_key($events, array_combine($higher_bound, $higher_bound))
					: [];

			$day_events = [];

			foreach ($filtered as $timestamp => $events_in_day) {
				foreach ($events_in_day as $event) {
					if ($event instanceof Event) {
						$event_start = Carbon::createFromTimestamp($event->start, $calendar->timezone);
						$event_end = Carbon::createFromTimestamp($event->end, $calendar->timezone);
						$first_day = intval(Carbon::createFromTimestamp($timestamp, $calendar->timezone)->endOfDay()->day);

						if ('yes' == get_post_meta($calendar->id, '_default_calendar_expand_multi_day_events', true)) {
							for ($day = $event_start->copy(); $day->lte($event_end->endOfDay()); $day->addDay()) {
								if ($day->month == $current_month && $day->year == $current_year) {
									$day_key = intval($day->format('j'));
									$event_id = $event->uid;
									/*
									 * The purpose of this condition is to determine if an event ends at the very start of the day.
									 */
									if ($day->isSameDay($event_end) && $event_end->hour == 0 && $event_end->minute == 0) {
										continue;
									}

									if (!isset($day_events[$day_key][$event_id])) {
										$day_events[$day_key][$event_id] = $event;
									}
								}
							}
						} else {
							$day_events[$first_day][] = $event;
						}
					}
				}
			}

			foreach ($day_events as $day_key => $events) {
				$day_events[$day_key] = array_values($events);
			}

			ksort($day_events, SORT_NUMERIC);
		}

		ob_start();

		echo '<tbody class="simcal-month simcal-month-' . $month . '">' . "\n";
		echo "\t" . '<tr class="simcal-week simcal-week-' . $week_of_year . '">';

		$days_in_row = 0;
		// Week may start on an arbitrary day (sun, 0 - sat, 6).
		$week_day = $week_starts;

		// Calculate the number of void days needed before the first day of the month
		// We need to account for the difference between the actual day the month starts
		// and where it should appear based on the week start setting
		$void_days_needed = ($month_starts - $week_starts + 7) % 7;

		// Void days in first week.
		for ($a = 0; $a < $void_days_needed; $a++):
			$last_void_day_class = $a === $void_days_needed - 1 ? 'simcal-day-void-last' : '';

			echo '<td class="simcal-day simcal-day-void ' . $last_void_day_class . '"></td>' . "\n";

			// Reset day of the week count (sun, 0 - sat, 6).
			if ($week_day === 6) {
				$week_day = -1;
			}
			$week_day++;

			$days_in_row++;
		endfor;

		// After void days, $week_day should be equal to $month_starts
		$week_day = $month_starts;

		// Actual days of the month.
		for ($day = 1; $day <= $days_in_month; $day++):
			$count = 0;
			$calendar_classes = [];
			$day_classes = 'simcal-day-' . $day . ' simcal-weekday-' . $week_day;

			$border_style = $bg_color = $color = '';

			// Is this the present, the past or the future, Doc?
			if ($current_min <= $now && $current_max >= $now) {
				$day_classes .= ' simcal-today simcal-present simcal-day';
				$the_color = new Color($calendar->today_color);
				$bg_color = '#' . $the_color->getHex();
				$color = $the_color->isDark() ? '#ffffff' : '#000000';
				$border_style = ' style="border: 1px solid ' . $bg_color . ';"';
			} elseif ($current_max < $now) {
				$day_classes .= ' simcal-past simcal-day';
			} elseif ($current_min > $now) {
				$day_classes .= ' simcal-future simcal-day';
			}

			// Print events for the current day in loop, if found any.
			if (isset($day_events[$day])):
				$bullet_colors = [];

				$list_events = '<ul class="simcal-events">';

				usort($day_events[$day], [$this, 'cmp']);

				foreach ($day_events[$day] as $event):
					$event_classes = $event_visibility = '';

					if ($event instanceof Event):
						// Store the calendar id where the event belongs (useful in grouped calendar feeds)
						$calendar_class = 'simcal-events-calendar-' . strval($event->calendar);
						$calendar_classes[] = $calendar_class;

						$recurring = $event->recurrence ? 'simcal-event-recurring ' : '';
						$has_location = $event->venue ? 'simcal-event-has-location ' : '';

						$event_classes .= 'simcal-event ' . $recurring . $has_location . $calendar_class . ' simcal-tooltip';

						// Toggle some events visibility if more than optional limit.
						if ($calendar->events_limit > -1 && $count >= $calendar->events_limit):
							$event_classes .= ' simcal-event-toggled';
							$event_visibility = ' style="display: none"';
						endif;

						// Event title in list.
						$title = !empty($event->title) ? trim($event->title) : __('Event', 'google-calendar-events');
						if ($calendar->trim_titles >= 1) {
							$title =
								strlen($title) > $calendar->trim_titles
									? mb_substr($title, 0, $calendar->trim_titles) . '&hellip;'
									: $title;
						}

						// Event color.
						$bullet = '';
						//$bullet_color = '#000';
						$event_color = $event->get_color();
						if (!empty($event_color)) {
							$bullet = '<span style="color: ' . $event_color . ';">&#9632;</span> ';
							$bullet_colors[] = $event_color;
						} else {
							$bullet_colors[] = '#000';
						}

						// Event contents.
						$list_events .=
							"\t" .
							'<li class="' .
							$event_classes .
							'"' .
							$event_visibility .
							' itemscope itemtype="http://schema.org/Event">' .
							"\n";
						$list_events .= "\t\t" . '<span class="simcal-event-title">' . $bullet . $title . '</span>' . "\n";
						$list_events .=
							"\t\t" .
							'<div class="simcal-event-details simcal-tooltip-content" style="display: none;">' .
							$calendar->get_event_html($event) .
							'</div>' .
							"\n";
						$list_events .= "\t" . '</li>' . "\n";

						$count++;
					endif;
				endforeach;

				if ($current_min <= $now && $current_max >= $now) {
					$day_classes .= ' simcal-today-has-events';
				}
				$day_classes .= ' simcal-day-has-events simcal-day-has-' . strval($count) . '-events';

				if ($calendar_classes) {
					$day_classes .= ' ' . trim(implode(' ', array_unique($calendar_classes)));
				}

				$list_events .= '</ul>' . "\n";

				// Optional button to toggle hidden events in list.
				if ($calendar->events_limit > -1 && $count > $calendar->events_limit):
					$list_events .=
						'<button class="simcal-events-toggle"><i class="simcal-icon-down simcal-icon-animate"></i></button>';
				endif;

				// Empty cell for day with no events.
			else:
				$list_events = '<span class="simcal-no-events"></span>';
			endif;

			// The actual days with numbers and events in each row cell.
			echo '<td class="' . $day_classes . '" data-events-count="' . strval($count) . '">' . "\n";

			if ($color) {
				$day_style = ' style="background-color: ' . $bg_color . '; color: ' . $color . '"';
			} elseif ($count > 0) {
				$the_color = new Color($calendar->days_events_color);
				$color = !$color ? ($the_color->isDark() ? '#ffffff' : '#000000') : $color;
				$bg_color = !$bg_color ? '#' . $the_color->getHex() : $bg_color;
				$day_style = ' style="background-color: ' . $bg_color . '; color: ' . $color . '"';
			} else {
				$day_style = '';
			}

			echo "\t" . '<div' . $border_style . '>' . "\n";
			echo "\t\t" . '<span class="simcal-day-label simcal-day-number"' . $day_style . '>' . $day . '</span>' . "\n";
			echo "\t\t" . $list_events . "\n";
			echo "\t\t";
			echo '<span class="simcal-events-dots" style="display: none;">';

			// Event bullets for calendar mobile mode.
			for ($i = 0; $i < $count; $i++) {
				echo '<b style="color: ' . $bullet_colors[$i] . ';"> &bull; </b>';
			}

			echo '</span>' . "\n";
			echo "\t" . '</div>' . "\n";
			echo '</td>' . "\n";

			// Reset day of the week count (sun, 0 - sat, 6).
			if ($week_day === 6) {
				$week_day = -1;
			}
			$week_day++;

			// Reset count of days for this row (0-6).
			if ($days_in_row === 6):
				// Close the week row.
				echo '</tr>';

				// Open a new week row.
				if ($day < $days_in_month) {
					echo '<tr class="simcal-week simcal-week-' . $week_of_year++ . '">' . "\n";
				}

				$days_in_row = -1;
			endif;

			$days_in_row++;

			$current_min = Carbon::createFromTimestamp($current_min, $calendar->timezone)
				->addDay()
				->getTimestamp();
			$current_max = Carbon::createFromTimestamp($current_max, $calendar->timezone)
				->addDay()
				->getTimestamp();
		endfor;

		// Void days at the end of the month.
		$remainder_days = 6 - $days_in_row;

		for ($i = 0; $i <= $remainder_days; $i++) {
			$last_void_day_class = $i == $remainder_days ? 'simcal-day-void-last' : '';

			echo '<td class="simcal-day simcal-day-void ' . $last_void_day_class . '"></td>' . "\n";

			$week_day++;
		}

		echo "\t" . '</tr>' . "\n";
		echo '</tbody>' . "\n";

		return ob_get_clean();
	}

	/**
	 * Ajax callback to request a new month.
	 *
	 * @since 3.0.0
	 */
	public function draw_grid_ajax()
	{
		if (isset($_POST['month']) && isset($_POST['year']) && isset($_POST['id'])) {
			$month = absint($_POST['month']);
			$year = absint($_POST['year']);
			$id = absint($_POST['id']);

			// Security check: Verify the calendar exists and user has permission to view it
			$post = get_post($id);

			// Check if post exists and is a calendar post type
			if (!$post || $post->post_type !== 'calendar') {
				wp_send_json_error('Calendar not found.');
				return;
			}

			// Check if post is published (public) or user has permission to read it
			if ($post->post_status === 'publish') {
				// Public calendar - allow access
			} else {
				// Private calendar - check if user has permission
				if (!current_user_can('read_post', $id)) {
					wp_send_json_error('You do not have permission to view this calendar.');
					return;
				}
			}

			wp_send_json_success($this->draw_month($month, $year, $id));
		} else {
			wp_send_json_error('Missing arguments in default calendar grid ajax request.');
		}
	}

	/**
	 * Array filter callback.
	 *
	 * @since  3.0.0
	 * @access private
	 *
	 * @param  int $event Timestamp.
	 *
	 * @return bool
	 */
	private function filter_events_before($event)
	{
		return intval($event) >= intval($this->start);
	}

	/**
	 * Array filter callback.
	 *
	 * @since  3.0.0
	 * @access private
	 *
	 * @param  int $event Timestamp.
	 *
	 * @return bool
	 */
	private function filter_events_after($event)
	{
		return intval($event) < intval($this->end);
	}
}
