<?php
/** no direct access **/
defined('MECEXEC') or die();

/**
 * Webnus MEC render class.
 * @author Webnus <info@webnus.net>
 */
class MEC_render extends MEC_base
{
    public $db;
    public $main;
    public $file;
    public $settings;
    public $post_atts;

    /**
     * Constructor method
     * @author Webnus <info@webnus.net>
     */
    public function __construct()
    {
        // Add image size for list and carousel
        add_image_size('thumblist', '300', '300', true);
        add_image_size('thumbrelated', '500', '500', true);
        add_image_size('meccarouselthumb', '474', '324', true);
        add_image_size('gridsquare', '391', '260', true);
        add_image_size('tileview', '300', '400', true);

        // Import MEC skin class
        MEC::import('app.libraries.skins');

        // MEC main library
        $this->main = $this->getMain();

        // MEC file library
        $this->file = $this->getFile();

        // MEC DB library
        $this->db = $this->getDB();

        // MEC Settings
        $this->settings = $this->main->get_settings();
    }

    /**
     * Do the shortcode and return its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function shortcode($atts)
    {
        $calendar_id = isset($atts['id']) ? (int) $atts['id'] : 0;
        $atts['id'] = $calendar_id;

        global $MEC_Shortcode_id;
        $MEC_Shortcode_id = $calendar_id;
        $atts = apply_filters('mec_calendar_atts', $this->parse($calendar_id, $atts));

        $skin = $atts['skin'] ?? $this->get_default_layout();
        return $this->skin($skin, $atts);
    }

    /**
     * Do the shortcode and return its json output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function shortcode_json($atts)
    {
        $calendar_id = $atts['id'] ?? 0;
        $atts = apply_filters('mec_calendar_atts', $this->parse($calendar_id, $atts));

        $skin = $atts['skin'] ?? $this->get_default_layout();
        $json = $this->skin($skin, $atts);

        $path = MEC::import('app.skins.'.$skin, true, true);
        $skin_path = apply_filters('mec_skin_path', $skin);

        if($skin_path != $skin and $this->file->exists($skin_path)) $path = $skin_path;
        if(!$this->file->exists($path))
        {
            return esc_html__('Skin controller does not exist.', 'mec');
        }

        include_once $path;

        $skin_class_name = 'MEC_skin_'.$skin;

        // Create Skin Object Class
        $SKO = new $skin_class_name();

        // Initialize the skin
        $SKO->initialize($atts);

        $atts['content_html']  = $SKO->output();

        if( 'full_calendar' == $skin ) {

            $default_atts = $SKO->prepare_skin_options( $skin, $atts );
            $default_view = $default_atts['default_view'] ?? 'list';

            $skin_class_name = 'MEC_skin_'.$default_view;

            // Create Skin Object Class
            $default_SKO = new $skin_class_name();

            // Initialize the skin
            $default_SKO->initialize($default_atts);

            // Fetch the events
            $atts['content_json'] = $default_SKO->fetch();
        }else{

            // Fetch the events
            $atts['content_json'] = $SKO->fetch();
        }

        return $atts;
    }

    /**
     * Do the widget and return its output
     * @author Webnus <info@webnus.net>
     * @param int $calendar_id
     * @param array $atts
     * @return string
     */
    public function widget($calendar_id, $atts = array())
    {
        $atts = apply_filters('mec_calendar_atts', $this->parse($calendar_id, $atts));

        $skin = $atts['skin'] ?? $this->get_default_layout();
        return $this->skin($skin, $atts);
    }

    /**
     * Do the yearly_view skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vyear($atts = array())
    {
        $atts = apply_filters('mec_vyear_atts', $atts);
        $skin = 'yearly_view';

        return $this->skin($skin, $atts);
    }

    /**
     * Do the monthly_view skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vmonth($atts = array())
    {
        $atts = apply_filters('mec_vmonth_atts', $atts);
        $skin = 'monthly_view';

        return $this->skin($skin, $atts);
    }

    /**
     * Do the full_calendar skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vfull($atts = array())
    {
        $atts = apply_filters('mec_vfull_atts', $atts);
        $skin = 'full_calendar';

        return $this->skin($skin, $atts);
    }

    /**
     * Do the default_full_calendar skin and returns its output (archive page)
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vdefaultfull($atts = array())
    {
        $atts = apply_filters('mec_vdefaultfull_atts', $atts);
        $skin = 'default_full_calendar';

        return $this->skin($skin, $atts);
    }


    /**
     * Do the weekly_view skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vweek($atts = array())
    {
        $atts = apply_filters('mec_vweek_atts', $atts);
        $skin = 'weekly_view';

        return $this->skin($skin, $atts);
    }

    /**
     * Do the timetable skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vtimetable($atts = array())
    {
        $atts = apply_filters('mec_vtimetable_atts', $atts);
        $skin = 'timetable';

        return $this->skin($skin, $atts);
    }

    /**
     * Do the Masonry skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vmasonry($atts = array())
    {
        $atts = apply_filters('mec_vmasonry_atts', $atts);
        $skin = 'masonry';

        return $this->skin($skin, $atts);
    }

    /**
     * Do the daily_view skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vday($atts = array())
    {
        $atts = apply_filters('mec_vday_atts', $atts);
        $skin = 'daily_view';

        return $this->skin($skin, $atts);
    }

    /**
     * Do the map skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vmap($atts = array())
    {
        $atts = apply_filters('mec_vmap_atts', $atts);
        $skin = 'map';

        return $this->skin($skin, $atts);
    }

    /**
     * Do the list skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vlist($atts = array())
    {
        $atts = apply_filters('mec_vlist_atts', $atts);
        $skin = 'list';

        return $this->skin($skin, $atts);
    }

    /**
     * Do the tile skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vtile($atts = array())
    {
        $atts = apply_filters('mec_vtile_atts', $atts);
        $skin = 'tile';

        return $this->skin($skin, $atts);
    }

    /**
     * Do the custom skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @param string $type
     * @param boolean $category
     * @return string
     */
    public function vcustom($atts, $type = 'archive', $category = false)
    {
        $k = 'custom_'.$type;

        $shortcode = (isset($this->settings[$k]) && !empty($this->settings[$k])) ? stripslashes($this->settings[$k]) : '';

        // Add Category
        if($category and is_tax('mec_category') and get_queried_object_id()) $shortcode = str_replace(']', ' category="'.get_queried_object_id().'"]', $shortcode);

        if(trim($shortcode)) return do_shortcode($shortcode);
        return '';
    }

    /**
     * Do the grid skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vgrid($atts = array())
    {
        $atts = apply_filters('mec_vgrid_atts', $atts);
        $skin = 'grid';

        return $this->skin($skin, $atts);
    }

    /**
     * Do the agenda skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vagenda($atts = array())
    {
        $atts = apply_filters('mec_vagenda_atts', $atts);
        $skin = 'agenda';

        return $this->skin($skin, $atts);
    }

    /**
     * Do the agenda skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vgeneral_calendar($atts = array())
    {
        $atts = array('is_categoy_page' => is_tax( 'mec_category' ));
        $atts = apply_filters('mec_vgeneral_calendar_atts', $atts);
        $skin = 'general_calendar';

        return $this->skin($skin, $atts);
    }

    public function get_skins()
    {
        return [
            'monthly_view',
            'full_calendar',
            'yearly_view',
            'weekly_view',
            'daily_view',
            'timetable',
            'masonry',
            'list',
            'grid',
            'agenda',
            'map',
            'general_calendar',
            'custom'
        ];
    }

    /**
     * Do the default archive skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vdefault($atts = array())
    {
        $skins = $this->get_skins();
        foreach($skins as $skin)
        {
            $skin_style = (isset($this->settings[$skin . '_archive_skin']) and trim($this->settings[$skin . '_archive_skin']) != '') ? $this->settings[$skin . '_archive_skin'] : null;
            if(!empty($skin_style))
            {
                $atts['sk-options'][ $skin ]['style'] = $skin_style;
            }
        }

        $monthly_skin = (isset($this->settings['monthly_view_archive_skin']) and trim($this->settings['monthly_view_archive_skin']) != '') ? $this->settings['monthly_view_archive_skin'] : 'clean';
        $list_skin = (isset($this->settings['list_archive_skin']) and trim($this->settings['list_archive_skin']) != '') ? $this->settings['list_archive_skin'] : 'standard';
        $grid_skin = (isset($this->settings['grid_archive_skin']) and trim($this->settings['grid_archive_skin']) != '') ? $this->settings['grid_archive_skin'] : 'classic';
        $timetable_skin = (isset($this->settings['timetable_archive_skin']) and trim($this->settings['timetable_archive_skin']) != '') ? $this->settings['timetable_archive_skin'] : 'modern';

        if(!isset($this->settings['default_skin_archive']) or (isset($this->settings['default_skin_archive']) and trim($this->settings['default_skin_archive']) == ''))
        {
            return $this->vdefaultfull($atts);
        }

        if($this->settings['default_skin_archive'] == 'monthly_view') $content = $this->vmonth(array_merge($atts, array('sk-options'=>array('monthly_view'=>array('style'=>$monthly_skin)))));
        elseif($this->settings['default_skin_archive'] == 'full_calendar') $content = $this->vdefaultfull($atts);
        elseif($this->settings['default_skin_archive'] == 'yearly_view') $content = $this->vyear($atts);
        elseif($this->settings['default_skin_archive'] == 'weekly_view') $content = $this->vweek($atts);
        elseif($this->settings['default_skin_archive'] == 'daily_view') $content = $this->vday($atts);
        elseif($this->settings['default_skin_archive'] == 'timetable') $content = $this->vtimetable(array_merge($atts, array('sk-options'=>array('timetable'=>array('style'=>$timetable_skin)))));
        elseif($this->settings['default_skin_archive'] == 'masonry') $content = $this->vmasonry($atts);
        elseif($this->settings['default_skin_archive'] == 'list') $content = $this->vlist(array_merge($atts, array('sk-options'=>array('list'=>array('style'=>$list_skin)))));
        elseif($this->settings['default_skin_archive'] == 'grid') $content = $this->vgrid(array_merge($atts, array('sk-options'=>array('grid'=>array('style'=>$grid_skin)))));
        elseif($this->settings['default_skin_archive'] == 'agenda') $content = $this->vagenda($atts);
        elseif($this->settings['default_skin_archive'] == 'map') $content = $this->vmap($atts);
        elseif($this->settings['default_skin_archive'] == 'general_calendar') $content = $this->vgeneral_calendar($atts);
        elseif($this->settings['default_skin_archive'] == 'custom') $content = $this->vcustom($atts);
        else $content = apply_filters('mec_default_skin_content', '');

        return $content;
    }

    /**
     * Do the single skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vsingle($atts)
    {
        // Force to array
        if(!is_array($atts)) $atts = [];

        // Get event ID
        $event_id = $atts['id'] ?? 0;

        $defaults = array('maximum_dates'=>$this->settings['booking_maximum_dates'] ?? 6);
        $atts = apply_filters('mec_vsingle_atts', $this->parse($event_id, wp_parse_args($atts, $defaults)));

        $skin = 'single';
        return $this->skin($skin, $atts);
    }

    /**
     * Do the category archive skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param array $atts
     * @return string
     */
    public function vcategory($atts = array())
    {
        // Skin
        $skin = (isset($this->settings['default_skin_category']) and trim($this->settings['default_skin_category']) != '') ? $this->settings['default_skin_category'] : 'list';

        $skins = $this->get_skins();
        foreach($skins as $sk)
        {
            $skin_style = (isset($this->settings[$sk . '_archive_skin']) and trim($this->settings[$sk . '_archive_skin']) != '') ? $this->settings[$sk . '_archive_skin'] : null;
            if(!empty($skin_style))
            {
                $atts['sk-options'][ $sk ]['style'] = $skin_style;
            }
        }

        // Show Only Expired Events
        if(isset($this->settings['category_events_method']) and $this->settings['category_events_method'] == 2) $atts['show_only_past_events'] = 1;

        $monthly_skin = (isset($this->settings['monthly_view_category_skin']) and trim($this->settings['monthly_view_category_skin']) != '') ? $this->settings['monthly_view_category_skin'] : 'classic';
        $list_skin = (isset($this->settings['list_category_skin']) and trim($this->settings['list_category_skin']) != '') ? $this->settings['list_category_skin'] : 'standard';
        $grid_skin = (isset($this->settings['grid_category_skin']) and trim($this->settings['grid_category_skin']) != '') ? $this->settings['grid_category_skin'] : 'classic';
        $timetable_skin = (isset($this->settings['timetable_category_skin']) and trim($this->settings['timetable_category_skin']) != '') ? $this->settings['timetable_category_skin'] : 'modern';

        if($skin == 'full_calendar') $content = $this->vfull($atts);
        elseif($skin == 'yearly_view') $content = $this->vyear($atts);
        elseif($skin == 'masonry') $content = $this->vmasonry($atts);
        elseif($skin == 'timetable') $content = $this->vtimetable(array_merge($atts, array('sk-options'=>array('timetable'=>array('style'=>$timetable_skin)))));
        elseif($skin == 'monthly_view') $content = $this->vmonth(array_merge($atts, array('sk-options'=>array('monthly_view'=>array('style'=>$monthly_skin)))));
        elseif($skin == 'weekly_view') $content = $this->vweek($atts);
        elseif($skin == 'daily_view') $content = $this->vday($atts);
        elseif($skin == 'list') $content = $this->vlist(array_merge($atts, array('sk-options'=>array('list'=>array('style'=>$list_skin)))));
        elseif($skin == 'grid') $content = $this->vgrid(array_merge($atts, array('sk-options'=>array('grid'=>array('style'=>$grid_skin)))));
        elseif($skin == 'agenda') $content = $this->vagenda($atts);
        elseif($skin == 'map') $content = $this->vmap($atts);
        elseif($skin == 'general_calendar') $content = $this->vgeneral_calendar($atts);
        elseif($skin == 'custom') $content = $this->vcustom($atts,'archive_category', true);
        else $content = apply_filters('mec_default_skin_content', '');

        return $content;
    }

    /**
     * Merge args
     * @author Webnus <info@webnus.net>
     * @param int $post_id
     * @param array $atts
     * @return array
     */
    public function parse($post_id, $atts = array())
    {
        if($this->post_atts) return wp_parse_args($atts, $this->post_atts);

        $post_atts = [];
        if($post_id) $post_atts = $this->main->get_post_meta($post_id);

        return wp_parse_args($atts, $post_atts);
    }

    /**
     * Run the skin and returns its output
     * @author Webnus <info@webnus.net>
     * @param string $skin
     * @param array $atts
     * @return string
     */
    public function skin($skin, $atts = array())
    {
        // Pro is Required for Some Skins
        if(!$this->main->getPRO() and in_array($skin, array('agenda', 'yearly_view', 'timetable', 'masonry', 'map', 'available_spot')))
        {
            return '';
        }

        $path = MEC::import('app.skins.'.$skin, true, true);
        $skin_path = apply_filters('mec_skin_path', $skin);

        if($skin_path != $skin and $this->file->exists($skin_path)) $path = $skin_path;
        if(!$this->file->exists($path))
        {
            return esc_html__('Skin controller does not exist.', 'mec');
        }

        include_once $path;

        $skin_class_name = 'MEC_skin_'.$skin;

        // Create Skin Object Class
        $SKO = new $skin_class_name();

        // Initialize the skin
        $SKO->initialize($atts);

        // Fetch the events
        $SKO->fetch();

        // Return the output
        return $SKO->output();
    }

    /**
     * Returns default skin
     * @author Webnus <info@webnus.net>
     * @return string
     */
    public function get_default_layout()
    {
        return apply_filters('mec_default_layout', 'list');
    }

    /**
     * Renders and returns all event data
     * @param int $post_id
     * @param string $content
     * @return \stdClass
     * @throws Exception
     * @author Webnus <info@webnus.net>
     */
    public function data($post_id, $content = NULL)
    {
        $cached = wp_cache_get($post_id, 'mec-events-data');
        if($cached) return $cached;

        $data = new stdClass();

        // Post Data
        $data->ID = $post_id;
        $data->title = get_the_title($post_id);
        $data->content = is_null($content) ? $this->main->get_post_content($post_id) : $content;

        // All Post Data
        $post = get_post($post_id);
        $data->post = $post;

        // All Meta Data
        $meta = $this->main->get_post_meta($post_id, true);
        if(isset($meta['mec_notifications'])) unset($meta['mec_notifications']);
        if(isset($meta['mec_fees']) and is_array($meta['mec_fees']) and isset($meta['mec_fees'][':i:'])) unset($meta['mec_fees'][':i:']);

        $data->meta = $meta;

        // All MEC Data
        $data->mec = $this->main->get_mec_events_data($post_id);

        $allday = $data->meta['mec_allday'] ?? 0;
        $hide_time = $data->meta['mec_hide_time'] ?? 0;
        $hide_end_time = $this->main->hide_end_time_status($post_id);

        $start_timestamp = ((isset($meta['mec_start_day_seconds']) and isset($meta['mec_start_date'])) ? (strtotime($meta['mec_start_date'])+$meta['mec_start_day_seconds']) : (isset($meta['mec_start_date']) ? strtotime($meta['mec_start_date']) : 0));
        $end_timestamp = ((isset($meta['mec_end_day_seconds']) and isset($meta['mec_end_date'])) ? (strtotime($meta['mec_end_date'])+$meta['mec_end_day_seconds']) : (isset($meta['mec_end_date']) ? strtotime($meta['mec_end_date']) : 0));

        $start_time = $this->main->get_time($start_timestamp);
        $end_time = $this->main->get_time($end_timestamp);

        if($hide_time)
        {
            $data->time = array(
                'start'=>'',
                'end'=>'',
                'start_raw'=>$start_time,
                'end_raw'=>$end_time,
                'start_timestamp'=>$start_timestamp,
                'end_timestamp'=>$end_timestamp,
            );
        }
        elseif($allday)
        {
            $data->time = array(
                'start'=>$this->main->m('all_day', esc_html__('All Day' , 'mec')),
                'end'=>'',
                'start_raw'=>$start_time,
                'end_raw'=>$end_time,
                'start_timestamp'=>$start_timestamp,
                'end_timestamp'=>$end_timestamp,
            );
        }
        else
        {
            $data->time = array(
                'start'=>$start_time,
                'end'=>($hide_end_time ? '' : $end_time),
                'start_raw'=>$start_time,
                'end_raw'=>$end_time,
                'start_timestamp'=>$start_timestamp,
                'end_timestamp'=>$end_timestamp,
            );
        }

        // Hourly Schedules
        $meta_hourly_schedules = $meta['mec_hourly_schedules'] ?? [];
        $first_key = key($meta_hourly_schedules);

        $hourly_schedules = [];
        if(count($meta_hourly_schedules) and !isset($meta_hourly_schedules[$first_key]['schedules']))
        {
            $hourly_schedules[] = array(
                'title' => esc_html__('Day 1', 'mec'),
                'schedules'=>$meta_hourly_schedules
            );
        }
        else $hourly_schedules = $meta_hourly_schedules;

        $data->hourly_schedules = $hourly_schedules;

        $tickets = (isset($meta['mec_tickets']) and is_array($meta['mec_tickets'])) ? $meta['mec_tickets'] : [];
        if(isset($tickets[':i:'])) unset($tickets[':i:']);

        $new_tickets = [];
        foreach($tickets as $ticket_id => $ticket)
        {
            if(!is_numeric($ticket_id)) continue;

            $ticket['id'] = $ticket_id;
            $new_tickets[$ticket_id] = $ticket;
        }

        $data->tickets = $new_tickets;
        $data->color = $meta['mec_color'] ?? '';
        $data->permalink = ((isset($meta['mec_read_more']) and filter_var($meta['mec_read_more'], FILTER_VALIDATE_URL)) ? $meta['mec_read_more'] : get_post_permalink($post_id));

        // Thumbnails
        $thumbnail = get_the_post_thumbnail($post, 'thumbnail', array('data-mec-postid'=>$post_id));
        $thumblist = get_the_post_thumbnail($post, 'thumblist' , array('data-mec-postid'=>$post_id));
        $gridsquare = get_the_post_thumbnail($post, 'gridsquare' , array('data-mec-postid'=>$post_id));
        $meccarouselthumb = get_the_post_thumbnail($post, 'meccarouselthumb' , array('data-mec-postid'=>$post_id));
        $medium = get_the_post_thumbnail($post, 'medium', array('data-mec-postid'=>$post_id));
        $large = get_the_post_thumbnail($post, 'large', array('data-mec-postid'=>$post_id));
        $full = get_the_post_thumbnail($post, 'full', array('data-mec-postid'=>$post_id));
        $tileview = get_the_post_thumbnail($post, 'tileview', array('data-mec-postid'=>$post_id));

        if(trim($thumbnail) == '' and trim($medium) != '') $thumbnail = preg_replace("/height=\"[0-9]*\"/", 'height="150"', preg_replace("/width=\"[0-9]*\"/", 'width="150"', $medium));
        elseif(trim($thumbnail) == '' and trim($large) != '') $thumbnail = preg_replace("/height=\"[0-9]*\"/", 'height="150"', preg_replace("/width=\"[0-9]*\"/", 'width="150"', $large));

        $dataThumbnails = apply_filters('mec-render-data-thumbnails', [
            'thumbnail'=>$thumbnail,
            'thumblist'=>$thumblist,
            'gridsquare'=>$gridsquare,
            'meccarouselthumb'=>$meccarouselthumb,
            'medium'=>$medium,
            'large'=>$large,
            'full'=>$full,
            'tileview'=>$tileview
        ], $post_id);

        $data->thumbnails = $dataThumbnails;

        // Featured image URLs
        $dataFeaturedImage = apply_filters('mec-render-data-featured-image', array(
            'thumbnail'=>esc_url($this->main->get_post_thumbnail_url($post_id, 'thumbnail')),
            'thumblist'=>esc_url($this->main->get_post_thumbnail_url($post_id, 'thumblist' )),
            'gridsquare'=>esc_url($this->main->get_post_thumbnail_url($post_id, 'gridsquare' )),
            'meccarouselthumb'=>esc_url($this->main->get_post_thumbnail_url($post_id, 'meccarouselthumb')),
            'medium'=>esc_url($this->main->get_post_thumbnail_url($post_id, 'medium')),
            'large'=>esc_url($this->main->get_post_thumbnail_url($post_id, 'large')),
            'full'=>esc_url($this->main->get_post_thumbnail_url($post_id, 'full')),
            'tileview'=>esc_url($this->main->get_post_thumbnail_url($post_id, 'tileview'))
        ), $post_id);

        $data->featured_image = $dataFeaturedImage;

        $taxonomies = array('mec_label', 'mec_location', 'mec_category', apply_filters('mec_taxonomy_tag', ''));

        if(!isset($this->settings['organizers_status']) || $this->settings['organizers_status']) $taxonomies[] = 'mec_organizer';
        if($this->getPRO() and isset($this->settings['sponsors_status']) and $this->settings['sponsors_status']) $taxonomies[] = 'mec_sponsor';

        $terms = wp_get_post_terms($post_id, $taxonomies, array('fields'=>'all'));
        foreach($terms as $term)
        {
            // First Validation
            if(!isset($term->taxonomy)) continue;

            if($term->taxonomy == 'mec_label') $data->labels[$term->term_id] = array('id'=>$term->term_id, 'name'=>$term->name, 'color'=>get_metadata('term', $term->term_id, 'color', true), 'style'=>get_metadata('term', $term->term_id, 'style', true));
            elseif($term->taxonomy == 'mec_organizer') $data->organizers[$term->term_id] = array('id'=>$term->term_id, 'name'=>$term->name, 'tel'=>get_metadata('term', $term->term_id, 'tel', true), 'email'=>get_metadata('term', $term->term_id, 'email', true), 'url'=>get_metadata('term', $term->term_id, 'url', true), 'page_label'=>get_metadata('term', $term->term_id, 'page_label', true), 'thumbnail'=>get_metadata('term', $term->term_id, 'thumbnail', true));
            elseif($term->taxonomy == 'mec_location')
            {
                $locations = array('id'=>$term->term_id, 'name'=>$term->name, 'address'=>get_metadata('term', $term->term_id, 'address', true), 'opening_hour'=>get_metadata('term', $term->term_id, 'opening_hour', true), 'latitude'=>get_metadata('term', $term->term_id, 'latitude', true), 'longitude'=>get_metadata('term', $term->term_id, 'longitude', true), 'url'=>get_metadata('term', $term->term_id, 'url', true), 'tel'=>get_metadata('term', $term->term_id, 'tel', true), 'thumbnail'=>get_metadata('term', $term->term_id, 'thumbnail', true));
                $data->locations[$term->term_id] = apply_filters('mec_map_load_location_terms', $locations, $term);
            }
            elseif($term->taxonomy == 'mec_category')
            {
                $data->categories[$term->term_id] = array(
                    'id'=>$term->term_id,
                    'name'=>$term->name,
                    'icon'=>get_metadata('term', $term->term_id, 'mec_cat_icon', true),
                    'color'=>get_metadata('term', $term->term_id, 'mec_cat_color', true),
                );
            }
            elseif($term->taxonomy == apply_filters('mec_taxonomy_tag', '')) $data->tags[$term->term_id] = array('id'=>$term->term_id, 'name'=>$term->name);
            elseif($term->taxonomy == 'mec_sponsor')
            {
                $data->sponsors[$term->term_id] = array(
                    'id'=>$term->term_id,
                    'name'=>$term->name,
                    'link'=>get_metadata('term', $term->term_id, 'link', true),
                    'logo'=>get_metadata('term', $term->term_id, 'logo', true)
                );
            }
        }

        // Speakers
        if(isset($this->settings['speakers_status']) and $this->settings['speakers_status'])
        {
            $terms = wp_get_post_terms($post_id, 'mec_speaker', [
                'fields' => 'all',
                'orderby' => 'meta_value_num',
                'meta_key' => 'mec_index'
            ]);

            foreach($terms as $term)
            {
                $speaker_type = get_metadata('term', $term->term_id, 'type', true);

                $data->speakers[$term->term_id] = array(
                    'id'=>$term->term_id,
                    'name'=>$term->name,
                    'type'=>$speaker_type ?: 'person',
                    'job_title'=>get_metadata('term', $term->term_id, 'job_title', true),
                    'tel'=>get_metadata('term', $term->term_id, 'tel', true),
                    'email'=>get_metadata('term', $term->term_id, 'email', true),
                    'facebook'=>get_metadata('term', $term->term_id, 'facebook', true),
                    'twitter'=>get_metadata('term', $term->term_id, 'twitter', true),
                    'gplus'=>get_metadata('term', $term->term_id, 'gplus', true),
                    'thumbnail'=>get_metadata('term', $term->term_id, 'thumbnail', true)
                );
            }
        }

        // Event Fields
        $fields = $this->main->get_event_fields();
        if(!is_array($fields)) $fields = [];

        $fields_data = (isset($data->meta['mec_fields']) and is_array($data->meta['mec_fields'])) ? $data->meta['mec_fields'] : get_post_meta($post_id, 'mec_fields', true);
        if(!is_array($fields_data)) $fields_data = [];

        foreach($fields as $f => $field)
        {
            if(!is_numeric($f)) continue;
            if(!isset($field['label'])) continue;

            $field_value = isset($fields_data[$f]) ? (is_array($fields_data[$f]) ? implode(', ', $fields_data[$f]) : $fields_data[$f]) : '';
            $data->fields[] = array(
                'id' => $f,
                'type' => ($field['type'] ?? NULL),
                'label' => esc_html__(stripslashes($field['label']), 'mec'),
                'value' => stripslashes($field_value),
            );
        }

        // Timezone Object
        $data->TZO = $this->main->get_TZO($post_id);

        // Add mec event past index to array.
        $end_date = (isset($data->meta['mec_date']['end']) and isset($data->meta['mec_date']['end']['date'])) ? $data->meta['mec_date']['end']['date'] : current_time('Y-m-d H:i:s');

        $e_time = '';
        $e_time .= sprintf("%02d", ($data->meta['mec_date']['end']['hour'] ?? '6')).':';
        $e_time .= sprintf("%02d", ($data->meta['mec_date']['end']['minutes'] ?? '0'));
        $e_time .= isset($data->meta['mec_date']['end']['ampm']) ? trim($data->meta['mec_date']['end']['ampm']) : 'PM';

        $end_time = date('D M j Y G:i:s', strtotime($end_date.' '.$e_time));

        $d1 = new DateTime(current_time("D M j Y G:i:s"));
        $d2 = new DateTime($end_time);

        if($d2 < $d1) $data->meta['event_past'] = true;
        else $data->meta['event_past'] = false;

        // Apply Filters
        $data = apply_filters('mec_render_event_data', $data, $post_id);

        // Set to cache
        wp_cache_set($post_id, $data, 'mec-events-data', 43200);

        //Edited Occurrences
        $settings = $this->main->get_settings();
        $edit_per_occurrences=[];

        if(isset($settings['per_occurrences_status']) and $settings['per_occurrences_status'])
        {
            $occ = new MEC_feature_occurrences();
            $occurrences = $occ->get_all_occurrences($post_id);

            foreach ($occurrences as $occurrence)
            {
                $date_start=date('Y-m-d', $occurrence['occurrence']);
                $params=json_decode($occurrence['params'], true);
                $params['location'] = (isset($params['location_id']) ? $this->main->get_location_data($params['location_id']) : array());
                $params['organizer'] = (isset($params['organizer_id']) ? $this->main->get_organizer_data($params['organizer_id']) : array());
                $edit_per_occurrences[$date_start]=$params;
            }
        }

        $data->edited_occurrences=$edit_per_occurrences;

        return $data;
    }

    /**
     * @param $event
     * @param MEC_skins $skin
     * @param int $serie
     * @return mixed
     */
    public function after_render($event, $skin, $serie = 1)
    {
        // If event is custom days and current date is available
        if(isset($event->data) and isset($event->data->meta) and isset($event->data->meta['mec_repeat_type']) and $event->data->meta['mec_repeat_type'] === 'custom_days' and isset($event->data->mec) and isset($event->data->mec->days) and isset($event->date) and is_array($event->date) and isset($event->date['start']) and isset($event->date['start']['date']))
        {
            // Time is already available
            if(isset($event->date['start']['hour']))
            {
                $hide_time = $event->data->meta['mec_hide_time'] ?? 0;
                $hide_end_time = $this->main->hide_end_time_status($event->ID);

                $s_hour = $event->date['start']['hour'];
                if(strtoupper($event->date['start']['ampm']) == 'AM' and $s_hour == '0') $s_hour = 12;

                $e_hour = $event->date['end']['hour'];
                if(strtoupper($event->date['end']['ampm']) == 'AM' and $e_hour == '0') $e_hour = 12;

                $start_time = $event->date['start']['date'].' '.sprintf("%02d", $s_hour).':'.sprintf("%02d", $event->date['start']['minutes']).' '.$event->date['start']['ampm'];
                $end_time = $event->date['end']['date'].' '.sprintf("%02d", $e_hour).':'.sprintf("%02d", $event->date['end']['minutes']).' '.$event->date['end']['ampm'];

                $start_timestamp = strtotime($start_time);
                $end_timestamp = strtotime($end_time);

                $st = $this->main->get_time($start_timestamp);
                $et = $this->main->get_time($end_timestamp);

                $allday = $event->data->meta['mec_allday'] ?? 0;
                if($allday)
                {
                    $st = $this->main->m('all_day', esc_html__('All Day' , 'mec'));
                    $et = '';
                }

                $event->data->time = array(
                    'start'=>($hide_time ? '' : $st),
                    'end'=>(($hide_time or $hide_end_time) ? '' : $et),
                    'start_raw'=>$st,
                    'end_raw'=>$et,
                    'start_timestamp'=>$start_timestamp,
                    'end_timestamp'=>$end_timestamp,
                );
            }
            // Detect the time when not available
            else
            {
                $multiple_day_show_method = \MEC\Settings\Settings::getInstance()->get_settings('multiple_day_show_method');
                $days_str = $event->data->mec->days;

                if(trim($days_str))
                {
                    $original_start_date = $event->data->meta['mec_start_date'];
                    $p_start_date = $event->date['start']['date'];
                    $allday = $event->data->meta['mec_allday'] ?? 0;

                    // Do not change the hour if it is the first serie of the event
                    if(!($original_start_date == $p_start_date and $serie == 1))
                    {
                        if($original_start_date == $p_start_date ) $serie -= 1;
                        $periods = explode(',', $days_str);

                        $datetime_timestamp = strtotime($p_start_date);

                        $p = 0;
                        foreach($periods as $period)
                        {
                            $ex = explode(':', $period);
                            $s_date = $ex[0] ?? false;
                            $e_date = $ex[1] ?? false;

                            if(!$s_date || ($p_start_date!== $s_date && 'all_days' !== $multiple_day_show_method)) continue;

                            $sd_timestamp = strtotime($s_date);
                            if($e_date)
                            {
                                $ed_timestamp = strtotime($e_date);
                                if(!($datetime_timestamp >= $sd_timestamp && $datetime_timestamp <= $ed_timestamp && isset($ex[2]) && isset($ex[3]))) continue;
                            }

                            $p++;
                            if($p !== $serie) continue;

                            $pos = strpos($ex[2], '-');
                            if($pos !== false) $ex[2] = substr_replace($ex[2], ':', $pos, 1);

                            $pos = strpos($ex[3], '-');
                            if($pos !== false) $ex[3] = substr_replace($ex[3], ':', $pos, 1);

                            $start_time = $s_date . ' ' . str_replace('-', ' ', $ex[2]);
                            $end_time =  $e_date . ' ' . str_replace('-', ' ', $ex[3]);

                            $this->add_time_to_event($event, $start_time, $end_time, $allday);
                        }
                    }

                    // Do not show expired occurrences
                    if(!$skin->args['mec-past-events'])
                    {
                        $periods = explode(',', $days_str);
                        $current_time = current_time('timestamp');

                        if($event->data->time['start_timestamp'] < $current_time)
                        {
                            foreach($periods as $period)
                            {
                                $ex = explode(':', $period);
                                $s_date = $ex[0] ?? '';
                                $e_date = $ex[1] ?? '';

                                if(!$s_date or !$e_date) continue;

                                $s_time = $ex[2] ?? '';
                                $e_time = $ex[3] ?? '';

                                $pos = strpos($s_time, '-');
                                if($pos !== false) $s_time = substr_replace($s_time, ':', $pos, 1);

                                $pos = strpos($e_time, '-');
                                if($pos !== false) $e_time = substr_replace($e_time, ':', $pos, 1);

                                $start_time = trim($s_date . ' ' . str_replace('-', ' ', $s_time));
                                $end_time =  trim($e_date . ' ' . str_replace('-', ' ', $e_time));

                                if(strtotime($start_time) < $current_time) continue;

                                $this->add_time_to_event($event, $start_time, $end_time, $allday);
                            }
                        }
                    }
                }
            }
        }
        // If not custom days
        elseif(isset($event->data) and isset($event->data->time) and isset($event->data->time['start_raw']) and isset($event->data->time['end_raw']) and isset($event->date) and isset($event->date['start']) and isset($event->date['end']))
        {
            $start_time = $event->date['start']['date'].' '.$event->data->time['start_raw'];
            $end_time = $event->date['end']['date'].' '.$event->data->time['end_raw'];

            $start_timestamp = strtotime($start_time);
            $end_timestamp = strtotime($end_time);

            if((!$start_timestamp or !$end_timestamp) and isset($event->data->meta['mec_date']) and isset($event->data->meta['mec_date']['start']) and isset($event->data->meta['mec_date']['start']['hour']) and isset($event->data->meta['mec_date']['end']) and isset($event->data->meta['mec_date']['end']['hour']))
            {
                $start_time = $event->date['start']['date'].' '.sprintf("%02d", $event->data->meta['mec_date']['start']['hour']).':'.sprintf("%02d", $event->data->meta['mec_date']['start']['minutes']).' '.$event->data->meta['mec_date']['start']['ampm'];
                $end_time = $event->date['end']['date'].' '.sprintf("%02d", $event->data->meta['mec_date']['end']['hour']).':'.sprintf("%02d", $event->data->meta['mec_date']['end']['minutes']).' '.$event->data->meta['mec_date']['end']['ampm'];

                $start_timestamp = strtotime($start_time);
                $end_timestamp = strtotime($end_time);
            }

            if($start_timestamp and $end_timestamp)
            {
                $event->data->time['start_timestamp'] = $start_timestamp;
                $event->data->time['end_timestamp'] = $end_timestamp;
            }
        }

        // Fill Start and End Dates
        if(!isset($event->date['start']['hour']) or !isset($event->date['end']['hour']))
        {
            $s_hour = $s_minutes = $s_ampm = $e_hour = $e_minutes = $e_ampm = NULL;

            if(isset($event->data->meta['mec_date']) and isset($event->data->meta['mec_date']['start']) and is_array($event->data->meta['mec_date']['start']))
            {
                $s_hour = sprintf("%02d", $event->data->meta['mec_date']['start']['hour']);
                $s_minutes = sprintf("%02d", $event->data->meta['mec_date']['start']['minutes']);
                $s_ampm = strtolower($event->data->meta['mec_date']['start']['ampm']);
            }

            if(isset($event->data->meta['mec_date']) and isset($event->data->meta['mec_date']['end']) and is_array($event->data->meta['mec_date']['end']))
            {
                $e_hour = sprintf("%02d", $event->data->meta['mec_date']['end']['hour']);
                $e_minutes = sprintf("%02d", $event->data->meta['mec_date']['end']['minutes']);
                $e_ampm = strtolower($event->data->meta['mec_date']['end']['ampm']);
            }

            if(isset($event->data->time) and isset($event->data->time['start_timestamp']) and $event->data->time['start_timestamp'])
            {
                $s_hour = date('h', $event->data->time['start_timestamp']);
                $s_minutes = date('i', $event->data->time['start_timestamp']);
                $s_ampm = date('a', $event->data->time['start_timestamp']);
            }

            if(isset($event->data->time) and isset($event->data->time['end_timestamp']) and $event->data->time['end_timestamp'])
            {
                $e_hour = date('h', $event->data->time['end_timestamp']);
                $e_minutes = date('i', $event->data->time['end_timestamp']);
                $e_ampm = date('a', $event->data->time['end_timestamp']);
            }

            $start_time = $event->date['start']['date'].' '.sprintf("%02d", $s_hour).':'.sprintf("%02d", $s_minutes).' '.$s_ampm;
            $end_time = $event->date['end']['date'].' '.sprintf("%02d", $e_hour).':'.sprintf("%02d", $e_minutes).' '.$e_ampm;

            if($s_hour and $s_minutes and $s_ampm and strtotime($start_time))
            {
                $d = ((isset($event->date['start']) and is_array($event->date['start'])) ? $event->date['start'] : array());
                $event->date['start'] = array_merge($d, array(
                    'hour' => sprintf("%02d", $s_hour),
                    'minutes' => sprintf("%02d", $s_minutes),
                    'ampm' => $s_ampm,
                    'timestamp' => strtotime($start_time),
                ));
            }

            if($e_hour and $e_minutes and $e_ampm and strtotime($end_time))
            {
                $d = ((isset($event->date['end']) and is_array($event->date['end'])) ? $event->date['end'] : array());
                $event->date['end'] = array_merge($d, array(
                    'hour' => sprintf("%02d", $e_hour),
                    'minutes' => sprintf("%02d", $e_minutes),
                    'ampm' => $e_ampm,
                    'timestamp' => strtotime($end_time),
                ));
            }
        }

        if($skin->skin != 'single' and !($skin->multiple_days_method == 'first_day' or ($skin->multiple_days_method == 'first_day_listgrid' and in_array($skin->skin, array('list', 'grid', 'slider', 'carousel', 'agenda', 'tile')))))
        {
            // MEC Cache
            $cache = $this->getCache();

            // Cache Key
            $key = $event->data->ID.'-'.$event->date['end']['date'];

            // Is Midnight Event
            $midnight = $this->main->is_midnight_event($event);

            // Improve Time for Multiple Day Events
            if($cache->has($key) or ($event->date['start']['date'] !== $event->date['end']['date'] and !$midnight))
            {
                $allday = $event->data->meta['mec_allday'] ?? 0;
                $hide_time = $event->data->meta['mec_hide_time'] ?? 0;
                $hide_end_time = $this->main->hide_end_time_status($event->ID);

                // Get From Cache (Last Day)
                if($cache->has($key) and $event->date['start']['date'] === $event->date['end']['date'])
                {
                    list($new_start_time, $new_end_time) = $cache->get($key);

                    // Delete the Cache
                    $cache->delete($key);
                }
                // Get From Cache (Between Days)
                elseif($cache->has($key) and $event->date['start']['date'] !== $event->date['end']['date'])
                {
                    $new_start_time = $this->main->get_time(0);
                    $new_end_time = $this->main->get_time((24*3600));

                    $allday = 1;
                }
                // First Day
                else
                {
                    $new_start_time = $event->data->time['start_raw'];
                    $new_end_time = $this->main->get_time((24*3600));
                    $second_start_time = $this->main->get_time(0);
                    $second_end_time = $event->data->time['end_raw'];

                    // Set to Cache
                    $cache->set($key, array($second_start_time, $second_end_time));
                }

                // Flag to Multiple Day
                $event->data->multipleday = 1;

                $event->data->time['start_raw'] = $new_start_time;
                $event->data->time['end_raw'] = $new_end_time;

                if($hide_time)
                {
                    $event->data->time['start'] = '';
                    $event->data->time['end'] = '';
                }
                elseif($allday)
                {
                    $event->data->time['start'] = $this->main->m('all_day', esc_html__('All Day' , 'mec'));
                    $event->data->time['end'] = '';
                }
                else
                {
                    $event->data->time['start'] = $new_start_time;
                    $event->data->time['end'] = ($hide_end_time ? '' : $new_end_time);
                }
            }
        }

        return $event;
    }

    public function add_time_to_event(&$event, $start_datetime, $end_datetime, $allday = false)
    {
        $hide_time = $event->data->meta['mec_hide_time'] ?? 0;
        $hide_end_time = $this->main->hide_end_time_status($event->ID);

        $start_timestamp = strtotime($start_datetime);
        $end_timestamp = strtotime($end_datetime);

        $st = $this->main->get_time($start_timestamp);
        $et = $this->main->get_time($end_timestamp);

        if($allday)
        {
            $st = $this->main->m('all_day', esc_html__('All Day' , 'mec'));
            $et = '';
        }

        $event->data->time = [
            'start'=>($hide_time ? '' : $st),
            'end'=>(($hide_time or $hide_end_time) ? '' : $et),
            'start_raw'=>$st,
            'end_raw'=>$et,
            'start_timestamp'=>$start_timestamp,
            'end_timestamp'=>$end_timestamp,
        ];
    }

    /**
     * Renders and Returns event dats
     * @author Webnus <info@webnus.net>
     * @param int $event_id
     * @param object $event
     * @param int $maximum
     * @param string $today
     * @return array
     */
    public function dates($event_id, $event = NULL, $maximum = 6, $today = '')
    {
        if(!$today) $today = date('Y-m-d');

        // Original Start Date
        $original_start_date = $today;
        $dates = [];

        // Get event data if it is NULL
        if(is_null($event))
        {
            $event = new stdClass();
            $event->meta = $this->main->get_post_meta($event_id, true);
            $event->mec = $this->db->select("SELECT * FROM `#__mec_events` WHERE `post_id`='$event_id'", "loadObject");
        }

        $start_date = $event->meta['mec_date']['start'] ?? [];
        $end_date = $event->meta['mec_date']['end'] ?? [];
        $first_occurrence = $event->meta['mec_start_date'] ?? $today;

        // Return empty array if date is not valid
        if(!isset($start_date['date']) or !strtotime($start_date['date'])) return $dates;

        // Return empty array if mec data is not exists on mec_events table
        if(!isset($event->mec->end)) return $dates;

        $allday = $event->meta['mec_allday'] ?? 0;
        $hide_time = $event->meta['mec_hide_time'] ?? 0;

        $event_period = $this->main->date_diff($start_date['date'], $end_date['date']);
        $event_period_days = $event_period ? $event_period->days : 0;

        $finish_date = array('date'=>$event->mec->end, 'hour'=>$event->meta['mec_date']['end']['hour'], 'minutes'=>$event->meta['mec_date']['end']['minutes'], 'ampm'=>$event->meta['mec_date']['end']['ampm']);

        $exceptional_days = (isset($event->mec->not_in_days) and trim($event->mec->not_in_days)) ? explode(',', trim($event->mec->not_in_days, ', ')) : [];
        $exceptional_days = $this->main->add_global_exceptional_days($exceptional_days);

        // Event Passed
        $past = $this->main->is_past($finish_date['date'], $today);

        // Event is not passed for custom days
        if($past and isset($event->meta['mec_repeat_type']) and $event->meta['mec_repeat_type'] == 'custom_days') $past = 0;

        // Normal event
        if(isset($event->mec->repeat) and $event->mec->repeat == '0')
        {
            $dates[] = $this->add_timestamps(array(
                'start'=>$start_date,
                'end'=>$end_date,
                'allday'=>$allday,
                'hide_time'=>$hide_time,
                'past'=>$past
            ));
        }
        elseif($past)
        {
            $dates[] = $this->add_timestamps(array(
                'start'=>$start_date,
                'end'=>$end_date,
                'allday'=>$allday,
                'hide_time'=>$hide_time,
                'past'=>$past
            ));
        }
        else
        {
            $repeat_type = $event->meta['mec_repeat_type'];

            if(in_array($repeat_type, array('daily', 'weekly')))
            {
                $repeat_interval = $event->meta['mec_repeat_interval'];

                $date_interval = $this->main->date_diff($start_date['date'], $today);
                $passed_days = $date_interval ? $date_interval->days : 0;

                // Check if date interval is negative (It means the event didn't start yet)
                if($date_interval and $date_interval->invert == 1) $remained_days_to_next_repeat = $passed_days;
                else $remained_days_to_next_repeat = $repeat_interval - fmod($passed_days, $repeat_interval);

                $start_date = date('Y-m-d', strtotime('+'.$remained_days_to_next_repeat.' Days', strtotime($today)));
                if(
                    !$this->main->is_date_after($finish_date['date'], $start_date) &&
                    $this->main->is_date_after($first_occurrence, $start_date, true) &&
                    !in_array($start_date, $exceptional_days)
                ) $dates[] = $this->add_timestamps(array(
                    'start'=>array('date'=>$start_date, 'hour'=>$event->meta['mec_date']['start']['hour'], 'minutes'=>$event->meta['mec_date']['start']['minutes'], 'ampm'=>$event->meta['mec_date']['start']['ampm']),
                    'end'=>array('date'=>date('Y-m-d', strtotime('+'.$event_period_days.' Days', strtotime($start_date))), 'hour'=>$event->meta['mec_date']['end']['hour'], 'minutes'=>$event->meta['mec_date']['end']['minutes'], 'ampm'=>$event->meta['mec_date']['end']['ampm']),
                    'allday'=>$allday,
                    'hide_time'=>$hide_time,
                    'past'=>0
                ));

                for($i = 2; $i <= $maximum; $i++)
                {
                    $start_date = date('Y-m-d', strtotime('+'.$repeat_interval.' Days', strtotime($start_date)));

                    // Event Not Started
                    if(!$this->main->is_date_after($first_occurrence, $start_date, true)) continue;

                    // Event finished
                    if($this->main->is_past($finish_date['date'], $start_date)) break;

                    if(!in_array($start_date, $exceptional_days)) $dates[] = $this->add_timestamps(array(
                        'start'=>array('date'=>$start_date, 'hour'=>$event->meta['mec_date']['start']['hour'], 'minutes'=>$event->meta['mec_date']['start']['minutes'], 'ampm'=>$event->meta['mec_date']['start']['ampm']),
                        'end'=>array('date'=>date('Y-m-d', strtotime('+'.$event_period_days.' Days', strtotime($start_date))), 'hour'=>$event->meta['mec_date']['end']['hour'], 'minutes'=>$event->meta['mec_date']['end']['minutes'], 'ampm'=>$event->meta['mec_date']['end']['ampm']),
                        'allday'=>$allday,
                        'hide_time'=>$hide_time,
                        'past'=>0
                    ));
                }
            }
            elseif(in_array($repeat_type, array('weekday', 'weekend', 'certain_weekdays')))
            {
                $date_interval = $this->main->date_diff($start_date['date'], $today);
                $passed_days = $date_interval ? $date_interval->days : 0;

                // Check if date interval is negative (It means the event didn't start yet)
                if($date_interval and $date_interval->invert == 1) $today = date('Y-m-d', strtotime('+'.$passed_days.' Days', strtotime($original_start_date)));

                $event_days = explode(',', trim($event->mec->weekdays, ', '));

                $today_id = date('N', strtotime($today));
                $found = 0;
                $i = 0;

                while($found < $maximum)
                {
                    if($this->main->is_past($finish_date['date'], $today)) break;

                    if(!in_array($today_id, $event_days))
                    {
                        $today = date('Y-m-d', strtotime('+1 Days', strtotime($today)));
                        $today_id = date('N', strtotime($today));

                        $i++;
                        continue;
                    }

                    $start_date = $today;
                    if(!in_array($start_date, $exceptional_days)) $dates[] = $this->add_timestamps(array(
                        'start'=>array('date'=>$start_date, 'hour'=>$event->meta['mec_date']['start']['hour'], 'minutes'=>$event->meta['mec_date']['start']['minutes'], 'ampm'=>$event->meta['mec_date']['start']['ampm']),
                        'end'=>array('date'=>date('Y-m-d', strtotime('+'.$event_period_days.' Days', strtotime($start_date))), 'hour'=>$event->meta['mec_date']['end']['hour'], 'minutes'=>$event->meta['mec_date']['end']['minutes'], 'ampm'=>$event->meta['mec_date']['end']['ampm']),
                        'allday'=>$allday,
                        'hide_time'=>$hide_time,
                        'past'=>0
                    ));

                    $today = date('Y-m-d', strtotime('+1 Days', strtotime($today)));
                    $today_id = date('N', strtotime($today));

                    $found++;
                    $i++;
                }
            }
            elseif($repeat_type == 'monthly')
            {
                $repeat_interval = ((isset($event->meta) and isset($event->meta['mec_repeat_interval'])) ? max(1, $event->meta['mec_repeat_interval']) : 1);

                // Start from Event Start Date
                if(strtotime($start_date['date']) > strtotime($original_start_date)) $original_start_date = $start_date['date'];

                $event_days = explode(',', trim($event->mec->day, ', '));
                $event_start_day = $event_days[0];

                $diff = $this->main->date_diff($start_date['date'], $end_date['date']);
                $event_period_days = $diff->days;

                $found = 0;
                $i = 0;

                while($found < $maximum)
                {
                    $t = strtotime('+'.$i.' Months', strtotime($original_start_date));
                    if(!$t) break;

                    $today = date('Y-m-d', $t);
                    if($this->main->is_past($finish_date['date'], $today)) break;

                    $year = date('Y', strtotime($today));
                    $month = date('m', strtotime($today));
                    $day = $event_start_day;
                    $hour = isset($event->meta['mec_date']['end']['hour']) ? sprintf('%02d', $event->meta['mec_date']['end']['hour']) : '06';
                    $minutes = isset($event->meta['mec_date']['end']['minutes']) ? sprintf('%02d', $event->meta['mec_date']['end']['minutes']) : '00';
                    $ampm = isset($event->meta['mec_date']['end']['ampm']) ? strtoupper($event->meta['mec_date']['end']['ampm']) : 'PM';

                    // Fix for 31st, 30th, 29th of some months
                    while(!checkdate((int) $month, (int) $day, (int) $year)) $day--;

                    $start_date = $year.'-'.$month.'-'.$day;
                    $end_time = $hour.':'.$minutes.' '.$ampm;

                    // Wrong Date & Time
                    if(!strtotime($start_date.' '.$end_time)) break;

                    if(strtotime($start_date.' '.$end_time) < strtotime($original_start_date))
                    {
                        $i += $repeat_interval;
                        continue;
                    }

                    if(!in_array($start_date, $exceptional_days)) $dates[] = $this->add_timestamps(array(
                        'start'=>array('date'=>$start_date, 'hour'=>$event->meta['mec_date']['start']['hour'], 'minutes'=>$event->meta['mec_date']['start']['minutes'], 'ampm'=>$event->meta['mec_date']['start']['ampm']),
                        'end'=>array('date'=>date('Y-m-d', strtotime('+'.$event_period_days.' Days', strtotime($start_date))), 'hour'=>$event->meta['mec_date']['end']['hour'], 'minutes'=>$event->meta['mec_date']['end']['minutes'], 'ampm'=>$event->meta['mec_date']['end']['ampm']),
                        'allday'=>$allday,
                        'hide_time'=>$hide_time,
                        'past'=>0
                    ));

                    $found++;
                    $i += $repeat_interval;
                }
            }
            elseif($repeat_type == 'yearly')
            {
                // Start from Event Start Date
                if(strtotime($start_date['date']) > strtotime($original_start_date)) $original_start_date = $start_date['date'];

                $event_days = explode(',', trim($event->mec->day, ', '));
                $event_months = explode(',', trim($event->mec->month, ', '));

                $event_start_day = $event_days[0];
                $event_period_days = $this->main->date_diff($start_date['date'], $end_date['date'])->days;

                $event_start_year = date('Y', strtotime($original_start_date));
                $event_start_month = date('n', strtotime($original_start_date));

                $found = 0;
                $i = 0;

                while($found < $maximum)
                {
                    $today = date('Y-m-d', strtotime($event_start_year.'-'.$event_start_month.'-'.$event_start_day));
                    if($this->main->is_past($finish_date['date'], $today)) break;

                    $year = date('Y', strtotime($today));
                    $month = date('m', strtotime($today));

                    if(!in_array($month, $event_months))
                    {
                        if($event_start_month == '12')
                        {
                            $event_start_month = 1;
                            $event_start_year += 1;
                        }
                        else $event_start_month += 1;

                        $i++;
                        continue;
                    }

                    $day = $event_start_day;

                    // Fix for 31st, 30th, 29th of some months
                    while(!checkdate($month, $day, $year)) $day--;

                    $event_date = $year.'-'.$month.'-'.$day;
                    if(strtotime($event_date) >= strtotime($original_start_date))
                    {
                        $start_date = $event_date;
                        if(!in_array($start_date, $exceptional_days)) $dates[] = $this->add_timestamps(array(
                            'start'=>array('date'=>$start_date, 'hour'=>$event->meta['mec_date']['start']['hour'], 'minutes'=>$event->meta['mec_date']['start']['minutes'], 'ampm'=>$event->meta['mec_date']['start']['ampm']),
                            'end'=>array('date'=>date('Y-m-d', strtotime('+'.$event_period_days.' Days', strtotime($start_date))), 'hour'=>$event->meta['mec_date']['end']['hour'], 'minutes'=>$event->meta['mec_date']['end']['minutes'], 'ampm'=>$event->meta['mec_date']['end']['ampm']),
                            'allday'=>$allday,
                            'hide_time'=>$hide_time,
                            'past'=>0
                        ));

                        $found++;
                    }

                    if($event_start_month == '12')
                    {
                        $event_start_month = 1;
                        $event_start_year += 1;
                    }
                    else $event_start_month += 1;

                    $i++;
                }
            }
            elseif($repeat_type == 'custom_days')
            {
                $custom_days = explode(',', $event->mec->days);

                // Add current time if we're checking today's events
                if($today == current_time('Y-m-d')) $today .= ' '.current_time('H:i:s');

                $found = 0;
                if((strtotime($event->mec->start) + $event->meta['mec_start_day_seconds']) >= strtotime($today) and !in_array($event->mec->start, $exceptional_days))
                {
                    $dates[] = $this->add_timestamps(array(
                        'start'=>array('date'=>$event->mec->start, 'hour'=>$event->meta['mec_date']['start']['hour'], 'minutes'=>$event->meta['mec_date']['start']['minutes'], 'ampm'=>$event->meta['mec_date']['start']['ampm']),
                        'end'=>array('date'=>$event->mec->end, 'hour'=>$event->meta['mec_date']['end']['hour'], 'minutes'=>$event->meta['mec_date']['end']['minutes'], 'ampm'=>$event->meta['mec_date']['end']['ampm']),
                        'allday'=>$allday,
                        'hide_time'=>$hide_time,
                        'past'=>0
                    ));

                    $found++;
                }

                foreach($custom_days as $custom_day)
                {
                    // Found maximum dates
                    if($found >= $maximum) break;

                    $cday = explode(':', $custom_day);

                    $c_start = $cday[0];
                    if(isset($cday[2])) $c_start .= ' '.str_replace('-', ' ', substr_replace($cday[2], ':', strpos($cday[2], '-'), 1));

                    // Date is past
                    if(strtotime($c_start) < strtotime($today)) continue;

                    $cday_start_hour = $event->meta['mec_date']['start']['hour'];
                    $cday_start_minutes = $event->meta['mec_date']['start']['minutes'];
                    $cday_start_ampm = $event->meta['mec_date']['start']['ampm'];

                    $cday_end_hour = $event->meta['mec_date']['end']['hour'];
                    $cday_end_minutes = $event->meta['mec_date']['end']['minutes'];
                    $cday_end_ampm = $event->meta['mec_date']['end']['ampm'];

                    if(isset($cday[2]) and isset($cday[3]))
                    {
                        $cday_start_ex = explode('-', $cday[2]);
                        $cday_start_hour = $cday_start_ex[0];
                        $cday_start_minutes = $cday_start_ex[1];
                        $cday_start_ampm = $cday_start_ex[2];

                        $cday_end_ex = explode('-', $cday[3]);
                        $cday_end_hour = $cday_end_ex[0];
                        $cday_end_minutes = $cday_end_ex[1];
                        $cday_end_ampm = $cday_end_ex[2];
                    }

                    if(!in_array($cday[0], $exceptional_days)) $dates[] = $this->add_timestamps(array(
                        'start'=>array('date'=>$cday[0], 'hour'=>$cday_start_hour, 'minutes'=>$cday_start_minutes, 'ampm'=>$cday_start_ampm),
                        'end'=>array('date'=>$cday[1], 'hour'=>$cday_end_hour, 'minutes'=>$cday_end_minutes, 'ampm'=>$cday_end_ampm),
                        'allday'=>$allday,
                        'hide_time'=>$hide_time,
                        'past'=>0
                    ));

                    $found++;
                }

                // No future date found so the event is passed
                if(!count($dates))
                {
                    $dates[] = $this->add_timestamps(array(
                        'start'=>$start_date,
                        'end'=>$finish_date,
                        'allday'=>$allday,
                        'hide_time'=>$hide_time,
                        'past'=>$past
                    ));
                }
            }
            elseif($repeat_type == 'advanced')
            {
                // Start from Event Start Date
                if(strtotime($start_date['date']) > strtotime($today)) $today = $start_date['date'];

                // Get user specifed days of month for repeat
                $advanced_days = get_post_meta($event_id, 'mec_advanced_days', true);

                // Generate dates for event
                $event_info = array('start' => $start_date, 'end' => $end_date, 'allday' => $allday, 'hide_time' => $hide_time, 'finish_date' => $finish_date['date'], 'exceptional_days' => $exceptional_days, 'mec_repeat_end' => ((isset($event->meta['mec_repeat']) and isset($event->meta['mec_repeat']['end'])) ? $event->meta['mec_repeat']['end'] : ''), 'occurrences' => ((isset($event->meta['mec_repeat']) and isset($event->meta['mec_repeat']['end_at_occurrences'])) ? $event->meta['mec_repeat']['end_at_occurrences'] : ''));

                $dates = $this->generate_advanced_days($advanced_days, $event_info, $maximum, $today);
            }
        }

        return $dates;
    }

    /**
     *  Render advanced dates
     * @author Webnus <info@webnus.net>
     * @param array $advanced_days
     * @param array $event_info
     * @param int $maximum
     * @param string $referer_date
     * @param string $mode
     * @return array
     */
    public function generate_advanced_days($advanced_days = array(), $event_info = array(), $maximum = 6, $referer_date = NULL, $mode = 'render')
    {
        if(!count($advanced_days)) return [];
        if(!trim($referer_date)) $referer_date = date('Y-m-d', current_time('timestamp', 0));

        $levels = array('first', 'second', 'third', 'fourth', 'last');
        $year = date('Y', strtotime($event_info['start']['date']));
        $dates = [];

        // Set last month for include current month results
        $month = date('m', strtotime('first day of last month', strtotime($event_info['start']['date'])));

        if($month == '12') $year = $year - 1;

        $maximum = intval($maximum);
        $i = 0;

        // Event info
        $exceptional_days =  array_key_exists('exceptional_days', $event_info) ? $event_info['exceptional_days'] : [];
        $start_date = $event_info['start'];
        $end_date = $event_info['end'];
        $allday = array_key_exists('allday', $event_info) ? $event_info['allday'] : 0;
        $hide_time = array_key_exists('hide_time', $event_info) ? $event_info['hide_time'] : 0;
        $finish_date = array_key_exists('finish_date', $event_info) ? $event_info['finish_date'] : '0000-00-00';
        $event_period = $this->main->date_diff($start_date['date'], $end_date['date']);
        $event_period_days = $event_period ? $event_period->days : 0;
        $mec_repeat_end = array_key_exists('mec_repeat_end', $event_info) ? $event_info['mec_repeat_end'] : '';
        $occurrences = array_key_exists('occurrences', $event_info) ? $event_info['occurrences'] : 0;

        // Include default start date to results
        if(!$this->main->is_past($start_date['date'], $referer_date) and !in_array($start_date['date'], $exceptional_days))
        {
            $dates[] = $this->add_timestamps(array(
                'start' => $start_date,
                'end' => $end_date,
                'allday' => $allday,
                'hide_time' => $hide_time,
                'past' => 0,
            ));

            if($mode == 'render') $i++;
        }

        while($i < $maximum)
        {
            $start = NULL;

            foreach($advanced_days as $day)
            {
                if($i >= $maximum) break;

                // Explode $day value for example (Sun.1) to Sun and 1
                $d = explode('.', $day);

                // Set indexes for {$levels} index if number day is Last(Sun.l) then indexes set 4th {$levels} index
                $index = intval($d[1]) ? (intval($d[1]) - 1) : 4;

                // Generate date
                $date = date('Y-m-t', strtotime("{$year}-{$month}-01"));

                // Generate start date for example "first Sun of next month"
                $start = date('Y-m-d', strtotime("{$levels[$index]} {$d[0]} of next month", strtotime($date)));
                $end = date('Y-m-d', strtotime("+{$event_period_days} Days", strtotime($start)));

                // Occurence equals to the main start date
                if($start === $start_date['date']) continue;

                // When ends repeat date set
                if($mode == 'render' and $this->main->is_past($finish_date, $start)) continue;

                // Jump to next level if start date is past
                if($this->main->is_past($start, $referer_date) or in_array($start, $exceptional_days)) continue;

                // Add dates
                $dates[] = $this->add_timestamps(array(
                    'start' => array(
                        'date' => $start,
                        'hour' => $start_date['hour'],
                        'minutes' => $start_date['minutes'],
                        'ampm' => $start_date['ampm'],
                    ),
                    'end' => array(
                        'date' => $end,
                        'hour' => $end_date['hour'],
                        'minutes' => $end_date['minutes'],
                        'ampm' => $end_date['ampm'],
                    ),
                    'allday' => $allday,
                    'hide_time' => $hide_time,
                    'past' => 0,
                ));

                $i++;
            }

            // When ends repeat date set
            if($mode == 'render' and $this->main->is_past($finish_date, $start)) break;

            // Change month and years for next resualts
            if(intval($month) == 12)
            {
                $year = intval($year) + 1;
                $month = '00';
            }

            $month = sprintf("%02d", intval($month) + 1);
        }

        if(($mode == 'render') and (trim($mec_repeat_end) == 'occurrences') and (count($dates) > $occurrences))
        {
            $max = strtotime(reset($dates)['start']['date']);
            $pos = 0;

            for($i = 1; $i < count($dates); $i++)
            {
                if(strtotime($dates[$i]['start']['date']) > $max)
                {
                    $max = strtotime($dates[$i]['start']['date']);
                    $pos = $i;
                }
            }

            unset($dates[$pos]);
        }

        // Remove Duplicates
        $uniques = [];
        $timestamps = [];

        foreach($dates as $key => $date)
        {
            $start_timestamp = $date['start']['timestamp'];
            $end_timestamp = $date['end']['timestamp'];
            $timestamp_key = $start_timestamp.'-'.$end_timestamp;

            if(isset($timestamps[$timestamp_key])) continue;

            $timestamps[$timestamp_key] = true;
            $uniques[] = $date;
        }

        // Sort
        usort($uniques, array($this, 'sort_dates'));

        return $uniques;
    }

    public function sort_dates($a, $b)
    {
        $a_timestamp = $a['start']['timestamp'];
        $b_timestamp = $b['end']['timestamp'];

        if($a_timestamp == $b_timestamp) return 0;
        return ($a_timestamp > $b_timestamp) ? +1 : -1;
    }

    /**
     * Render markers
     * @author Webnus <info@webnus.net>
     * @param array $events
     * @param string $skin_style
     * @return array
     */
    public function markers($events, $skin_style = 'classic')
    {
        $date_format = (isset($this->settings['google_maps_date_format1']) and trim($this->settings['google_maps_date_format1'])) ? $this->settings['google_maps_date_format1'] : 'M d Y';

        $requested_location_id = isset($_REQUEST['sf'], $_REQUEST['sf']['location']) ? $_REQUEST['sf']['location'] : null;

        $markers = [];
        foreach($events as $event)
        {
            if(!is_object($event)) continue;
            if(!isset($event->data->locations) or (isset($event->data->locations) and !is_array($event->data->locations))) continue;

            $locations = [];

            $main_location_id = get_post_meta($event->ID, 'mec_location_id', true);
            if(isset($event->date, $event->date['start'], $event->date['start']['timestamp'])) $main_location_id = MEC_feature_occurrences::param($event->ID, $event->date['start']['timestamp'], 'location_id', $main_location_id);

            $main_location_id = apply_filters('wpml_object_id', $main_location_id, 'mec_location', true);

            if($main_location_id && $main_location_id > 1) $locations[] = $main_location_id;

            $additional_location_ids = get_post_meta($event->ID, 'mec_additional_location_ids', true);
            if(!is_array($additional_location_ids)) $additional_location_ids = [];

            $locations = array_merge($locations, $additional_location_ids);
            $locations = array_unique($locations);

            if($requested_location_id && !is_array($requested_location_id))
            {
                if($main_location_id !== $requested_location_id && !in_array($requested_location_id, $additional_location_ids)) continue;
                $locations = [$requested_location_id];
            }

            // No Locations
            if(!count($locations)) continue;

            foreach($locations as $location_id)
            {
                $location_id = (int) $location_id;

                $latitude = get_term_meta($location_id, 'latitude', true);
                $longitude = get_term_meta($location_id, 'longitude', true);

                // No latitude/Longitude
                if(trim($latitude) == '' or trim($longitude) == '') continue;

                $location = get_term($location_id);
                $name = $location->name ?? '';

                $latitude = floatval($latitude);
                $longitude = floatval($longitude);

                $key = $latitude.','.$longitude;
                if(!isset($markers[$key]))
                {
                    $markers[$key] = array(
                        'latitude'=>$latitude,
                        'longitude'=>$longitude,
                        'name'=>$name,
                        'address'=>get_term_meta($location_id, 'address', true),
                        'event_ids'=>array($event->data->ID),
                        'lightbox'=>$this->main->get_marker_lightbox($event, $date_format, $skin_style),
                    );
                }
                else
                {
                    $markers[$key]['event_ids'][] = $event->data->ID;
                    $markers[$key]['lightbox'] .= $this->main->get_marker_lightbox($event, $date_format, $skin_style);
                }
            }
        }

        $points = [];
        foreach($markers as $key=>$marker)
        {
            $points[$key] = $marker;

            $points[$key]['lightbox'] = '<div><div class="mec-event-detail mec-map-view-event-detail"><i class="mec-sl-map-marker"></i> '.(trim($marker['address']) ? esc_html($marker['address']) : esc_html($marker['name'])).'</div><div>'.MEC_kses::element($marker['lightbox']).'</div></div>';
            $points[$key]['count'] = count($marker['event_ids']);
            $points[$key]['infowindow'] = $this->main->get_marker_infowindow($marker);
        }

        return apply_filters('mec_render_markers', $points);
    }

    public function add_timestamps($date)
    {
        $start = (isset($date['start']) and is_array($date['start'])) ? $date['start'] : [];
        $end = (isset($date['end']) and is_array($date['end'])) ? $date['end'] : [];

        if(!count($start) or !count($end)) return $date;

        $s_hour = $start['hour'];
        if(strtoupper($start['ampm']) == 'AM' and $s_hour == '0') $s_hour = 12;

        $e_hour = $end['hour'];
        if(strtoupper($end['ampm']) == 'AM' and $e_hour == '0') $e_hour = 12;

        $allday = ($date['allday'] ?? 0);

        // All Day Event
        if($allday)
        {
            $s_hour = 12;
            $start['minutes'] = 1;
            $start['ampm'] = 'AM';

            $e_hour = 11;
            $end['minutes'] = 59;
            $end['ampm'] = 'PM';
        }

        $start_time = $start['date'].' '.sprintf("%02d", $s_hour).':'.sprintf("%02d", $start['minutes']).' '.$start['ampm'];
        $end_time = $end['date'].' '.sprintf("%02d", $e_hour).':'.sprintf("%02d", $end['minutes']).' '.$end['ampm'];

        $start['timestamp'] = strtotime($start_time);
        $end['timestamp'] = strtotime($end_time);

        $hide_time = $date['hide_time'] ?? 0;
        $past = $date['past'] ?? 0;

        return array(
            'start' => $start,
            'end' => $end,
            'allday' => $allday,
            'hide_time' => $hide_time,
            'past' => $past,
        );
    }
}
