<?php

/**
 * The concrete class for the calendar page.
 *
 * @author     Time.ly Network Inc.
 * @since      2.0
 *
 * @package    AI1EC
 * @subpackage AI1EC.View
 */
class Ai1ec_Calendar_Page extends Ai1ec_Base {

    /**
     * @var Ai1ec_Memory_Utility Instance of memory to hold exact dates
     */
    protected $_exact_dates = NULL;

    /**
     * Public constructor
     *
     * @param Ai1ec_Registry_Object $registry The registry object
     */
    public function __construct( Ai1ec_Registry_Object $registry ) {
        parent::__construct( $registry );
        $this->_exact_dates = $registry->get( 'cache.memory' );
    }

    /**
     * Get the content if the calendar page
     *
     * @param Ai1ec_Request_Parser $request Request object.
     * @param string               $caller  Method caller, expected one of
     *                                      ['shortcode', 'render-command']
     *                                      Defaults to 'render-command'.
     *
     * @return string Content.
     */
    public function get_content(
        Ai1ec_Request_Parser $request,
        $caller = 'render-command'
    ) {
        // Get args for the current view; required to generate HTML for views
        // dropdown list, categories, tags, subscribe buttons, and of course the
        // view itself.
        $view_args  = $this->get_view_args_for_view( $request );

        try {
            $action   = $this->_registry->get( 'model.settings-view' )
                ->get_configured( $view_args['action'] );
        } catch ( Ai1ec_Settings_Exception $exception ) {
            // short-circuit and return error message
            return '<div id="ai1ec-container"><div class="timely"><p>' .
                Ai1ec_I18n::__(
                    'There was an error loading calendar. Please contact site administrator and inform him to configure calendar views.'
                ) .
                '</p></div></div>';
        }
        $type    = $request->get( 'request_type' );
        $is_json = $this->_registry->get( 'http.request' )->is_json_required(
            $view_args['request_format'], $action
        );

        // Add view-specific args to the current view args.
        $exact_date = $this->get_exact_date( $request );
        try {
            $view_obj = $this->_registry->get(
                'view.calendar.view.' . $action,
                $request
            );
        } catch ( Ai1ec_Bootstrap_Exception $exc ) {
            $this->_registry->get( 'notification.admin' )->store(
                sprintf(
                        Ai1ec_I18n::__( 'Calendar was unable to initialize %s view and has reverted to Agenda view. Please check if you have installed the latest versions of calendar add-ons.' ),
                        ucfirst( $action )
                ),
                'error',
                0,
                array( Ai1ec_Notification_Admin::RCPT_ADMIN ),
                true
            );
            // don't disable calendar - just switch to agenda which should
            // always exists
            $action   = 'agenda';
            $view_obj = $this->_registry->get(
                'view.calendar.view.' . $action,
                $request
            );
        }
        $view_args = $view_obj->get_extra_arguments( $view_args, $exact_date );

        // Get HTML for views dropdown list.
        $dropdown_args = $view_args;
        if (
            isset( $dropdown_args['time_limit'] ) &&
            false !== $exact_date
        ) {
            $dropdown_args['exact_date'] = $exact_date;
        }
        $views_dropdown =
            $this->get_html_for_views_dropdown( $dropdown_args, $view_obj );
        // Add views dropdown markup to view args.
        $view_args['views_dropdown'] = $views_dropdown;

        $settings = $this->_registry->get( 'model.settings' );
        if ( $settings->get( 'ai1ec_use_frontend_rendering' ) ) {
            $view_args['request_format'] = 'json';
        }

        // Get HTML for subscribe buttons.
        $subscribe_buttons = $this->get_html_for_subscribe_buttons( $view_args );

        // Get HTML for view itself.
        $view              = $view_obj->get_content( $view_args );

        $router            = $this->_registry->get( 'routing.router' );
        $are_filters_set   = $router->is_at_least_one_filter_set_in_request(
            $view_args
        );

        if (
            $is_json &&
            ( $view_args['no_navigation'] || $type !== 'html' )
        ) {
            // send data both for json and jsonp as shortcodes are jsonp
            return array(
                'html'               => $view,
                'views_dropdown'     => $views_dropdown,
                'subscribe_buttons'  => $subscribe_buttons,
                'are_filters_set'    => $are_filters_set,
                'is_json'            => $is_json,
            );

        } else {
            $loader     = $this->_registry->get( 'theme.loader' );
            $empty      = $loader->get_file( 'empty.twig', array(), false );
            // Get HTML for categories and for tags
            $taxonomy   = $this->_registry->get( 'view.calendar.taxonomy' );
            $categories = $taxonomy->get_html_for_categories(
                $view_args
            );
            $tags       = $taxonomy->get_html_for_tags(
                $view_args,
                true
            );

            // option to show filters in the super widget
            // Define new arguments for overall calendar view
            $filter_args = array(
                'views_dropdown'               => $views_dropdown,
                'categories'                   => $categories,
                'tags'                         => $tags,
                'contribution_buttons'         => apply_filters(
                    'ai1ec_contribution_buttons',
                    '',
                    $type,
                    $caller
                ),
                'additional_buttons'           => apply_filters(
                    'ai1ec_additional_buttons',
                    '',
                    $view_args
                ),
                'show_dropdowns'               => apply_filters(
                    'ai1ec_show_dropdowns',
                    true
                ),
                'show_select2'                 => apply_filters(
                    'ai1ec_show_select2',
                    false
                ),
                'span_for_select2'             => apply_filters(
                    'ai1ec_span_for_select2',
                    ''
                ),
                'authors'                      => apply_filters(
                    'ai1ec_authors',
                    ''
                ),
                'save_view_btngroup'           => apply_filters(
                    'ai1ec_save_view_btngroup',
                    $empty
                ),
                'view_args'                    => $view_args,
                'request'                      => $request,
            );

            $filter_menu   = $loader->get_file(
                'filter-menu.twig',
                $filter_args,
                false
            )->get_content();
            // hide filters in the SW
            if ( 'true' !== $request->get( 'display_filters' ) && 'jsonp' === $type ) {
                $filter_menu = '';
            }

            $calendar_args = array(
                'version'                      => AI1EC_VERSION,
                'filter_menu'                  => $filter_menu,
                'view'                         => $view,
                'subscribe_buttons'            => $subscribe_buttons,
                'disable_standard_filter_menu' => apply_filters(
                    'ai1ec_disable_standard_filter_menu',
                    false
                ),
                'inline_js_calendar'           => apply_filters(
                    'ai1ec_inline_js_calendar',
                    ''
                ),
                'after_view'                   => apply_filters(
                    'ai1ec_after_view',
                    ''
                ),
                'ai1ec_above_calendar'         => apply_filters(
                    'ai1ec_above_calendar',
                    ''
                ),
            );

            if ( is_array( $calendar_args['view'] ) ) {
                $view_args['request_format'] = 'html';
                $calendar_args['view']       = $view_obj->get_content( $view_args );
            }
            $calendar = $loader->get_file( 'calendar.twig', $calendar_args, false );
            // if it's just html, only the calendar html must be returned.
            if ( 'html' === $type ) {
                return $calendar->get_content();
            }
            // send data both for json and jsonp as shortcodes are jsonp
            return array(
                'html'               => $calendar->get_content(),
                'views_dropdown'     => $views_dropdown,
                'subscribe_buttons'  => $subscribe_buttons,
                'are_filters_set'    => $are_filters_set,
                'is_json'            => $is_json
            );
        }
    }

    /**
     * Render the HTML for the `subscribe' buttons.
     *
     * @param array $view_args Args to pass.
     *
     * @return string Rendered HTML to include in output.
     */
    public function get_html_for_subscribe_buttons( array $view_args ) {
        $settings           = $this->_registry->get( 'model.settings' );
        $turn_off_subscribe = $settings->get( 'turn_off_subscription_buttons' );
        if ( $turn_off_subscribe ) {
            return '';
        }

        $args = array(
            'url_args'           => '',
            'is_filtered'        => false,
            'export_url'         => AI1EC_EXPORT_URL,
            'export_url_no_html' => AI1EC_EXPORT_URL . '&no_html=true',
            'text_filtered'      => Ai1ec_I18n::__( 'Subscribe to filtered calendar' ),
            'text_subscribe'     => Ai1ec_I18n::__( 'Subscribe' ),
            'text_get_calendar'  => Ai1ec_I18n::__( 'Get a Timely Calendar' ),
            'show_get_calendar'  => ! $settings->get( 'disable_get_calendar_button' ),
            'text'               => $this->_registry
                ->get( 'view.calendar.subscribe-button' )
                ->get_labels(),
            'placement'          => 'up',
        );
        if ( ! empty( $view_args['cat_ids'] ) ) {
            $args['url_args'] .= '&ai1ec_cat_ids=' .
                implode( ',', $view_args['cat_ids'] );
            $args['is_filtered'] = true;
        }
        if ( ! empty( $view_args['tag_ids'] ) ) {
            $args['url_args']  .= '&ai1ec_tag_ids=' .
                implode( ',', $view_args['tag_ids'] );
            $args['is_filtered'] = true;
        }
        if ( ! empty( $view_args['post_ids'] ) ) {
            $args['url_args']  .= '&ai1ec_post_ids=' .
                implode( ',', $view_args['post_ids'] );
            $args['is_filtered'] = true;
        }
        $args = apply_filters(
            'ai1ec_subscribe_buttons_arguments',
            $args,
            $view_args
        );
        $localization = $this->_registry->get( 'p28n.wpml' );
        if (
            NULL !== ( $use_lang = $localization->get_language() )
        ) {
            $args['url_args'] .= '&lang=' . $use_lang;
        }
        $subscribe = $this->_registry->get( 'theme.loader' )
            ->get_file( 'subscribe-buttons.twig', $args, false );
        return $subscribe->get_content();
    }

    /**
     * This function generates the html for the view dropdowns.
     *
     * @param array                        $view_args Args passed to view
     * @param Ai1ec_Calendar_View_Abstract $view      View object
     */
    protected function get_html_for_views_dropdown(
        array $view_args,
        Ai1ec_Calendar_View_Abstract $view
    ) {
        $settings        = $this->_registry->get( 'model.settings' );
        $available_views = array();
        $enabled_views   = (array)$settings->get( 'enabled_views', array() );
        $view_names      = array();
        $mode            = wp_is_mobile() ? '_mobile' : '';
        foreach ( $enabled_views as $key => $val ) {
            $view_names[$key] = translate_nooped_plural(
                $val['longname'],
                1
            );
            // Find out if view is enabled in requested mode (mobile or desktop). If
            // no mode-specific setting is available, fall back to desktop setting.
            $view_enabled = isset( $enabled_views[$key]['enabled' . $mode] ) ?
                $enabled_views[$key]['enabled' . $mode] :
                $enabled_views[$key]['enabled'];
            $values = array();
            $options = $view_args;
            if ( $view_enabled ) {
                if ( $view instanceof Ai1ec_Calendar_View_Agenda ) {
                    if (
                        isset( $options['exact_date'] ) &&
                        ! isset( $options['time_limit'] )
                    ) {
                        $options['time_limit'] = $options['exact_date'];
                    }
                    unset( $options['exact_date'] );
                } else {
                    unset( $options['time_limit'] );
                }
                unset( $options['month_offset'] );
                unset( $options['week_offset'] );
                unset( $options['oneday_offset'] );
                $options['action'] = $key;
                $values['desc'] = translate_nooped_plural(
                    $val['longname'],
                    1
                );
                if ( $settings->get( 'ai1ec_use_frontend_rendering' ) ) {
                    $options['request_format'] = 'json';
                }
                $href = $this->_registry->get( 'html.element.href', $options );
                $values['href'] = $href->generate_href();
                $available_views[$key] = $values;
            }
        };
        $args = array(
            'view_names'              => $view_names,
            'available_views'         => $available_views,
            'current_view'            => $view_args['action'],
            'data_type'               => $view_args['data_type'],
        );

        $views_dropdown = $this->_registry->get( 'theme.loader' )
            ->get_file( 'views_dropdown.twig', $args, false );
        return $views_dropdown->get_content();
    }

    /**
     * Get the exact date from request if available, or else from settings.
     *
     * @param Ai1ec_Abstract_Query settings
     *
     * @return boolean|int
     */
    private function get_exact_date( Ai1ec_Abstract_Query $request ) {
        $settings = $this->_registry->get( 'model.settings' );

        // Preprocess exact_date.
        // Check to see if a date has been specified.
        $exact_date = $request->get( 'exact_date' );
        $use_key    = $exact_date;
        if ( null === ( $exact_date = $this->_exact_dates->get( $use_key ) ) ) {
            $exact_date = $use_key;
            // Let's check if we have a date
            if ( false !== $exact_date ) {
                // If it's not a timestamp
                if ( ! Ai1ec_Validation_Utility::is_valid_time_stamp( $exact_date ) ) {
                    // Try to parse it
                    $exact_date = $this->return_gmtime_from_exact_date( $exact_date );
                    if ( false === $exact_date ) {
                        return null;
                    }
                }
            }
            // Last try, let's see if an exact date is set in settings.
            if ( false === $exact_date && $settings->get( 'exact_date' ) !== '' ) {
                $exact_date = $this->return_gmtime_from_exact_date(
                    $settings->get( 'exact_date' )
                );
            }
            $this->_exact_dates->set( $use_key, $exact_date );
        }
        return $exact_date;
    }

    /**
     * Decomposes an 'exact_date' parameter into month, day, year components based
     * on date pattern defined in settings (assumed to be in local time zone),
     * then returns a timestamp in GMT.
     *
     * @param  string     $exact_date 'exact_date' parameter passed to a view
     * @return bool|int               false if argument not provided or invalid,
     *                                else UNIX timestamp in GMT
     */
    private function return_gmtime_from_exact_date( $exact_date ) {
        $input_format = $this->_registry->get( 'model.settings' )
            ->get( 'input_date_format' );

        $date = Ai1ec_Validation_Utility::format_as_iso(
            $exact_date,
            $input_format
        );
        if ( false === $date ) {
            $exact_date = false;
        } else {
            $exact_date = $this->_registry->get(
                'date.time',
                $date,
                'sys.default'
            )->format_to_gmt();
            if ( $exact_date < 0 ) {
                return false;
            }
        }
        return $exact_date;
    }

    /**
     * Returns the correct data attribute to use in views
     *
     * @param string $type
     */
    private function return_data_type_for_request_type( $type ) {
        $data_type = 'data-type="json"';
        if ( $type === 'jsonp' ) {
            $data_type = 'data-type="jsonp"';
        }
        return $data_type;
    }

    /**
     * Get the parameters for the view from the request object
     *
     * @param Ai1ec_Abstract_Query $request
     *
     * @return array
     */
    protected function get_view_args_for_view( Ai1ec_Abstract_Query $request ) {
        $settings = $this->_registry->get( 'model.settings' );
        // Define arguments for specific calendar sub-view (month, agenda, etc.)
        // Preprocess action.
        // Allow action w/ or w/o ai1ec_ prefix. Remove ai1ec_ if provided.
        $action = $request->get( 'action' );

        if ( 0 === strncmp( $action, 'ai1ec_', 6 ) ) {
            $action = substr( $action, 6 );
        }
        $view_args = $request->get_dict(
            apply_filters(
                'ai1ec_view_args_for_view',
                array(
                    'post_ids',
                    'auth_ids',
                    'cat_ids',
                    'tag_ids',
                    'events_limit',
                    'instance_ids',
                )
            )
        );
        $type = $request->get( 'request_type' );
        if ( 'html' === $type ) {
            $add_defaults = array(
                'cat_ids' => 'categories',
                'tag_ids' => 'tags',
            );
            foreach ( $add_defaults as $query => $default ) {
                if ( empty( $view_args[$query] ) ) {
                    $setting = $settings->get( 'default_tags_categories' );
                    if ( isset( $setting[$default] ) ) {
                        $view_args[$query] = $setting[$default];
                    }
                }
            }
        }

        $view_args['data_type'] = $this->return_data_type_for_request_type(
            $type
        );

        $view_args['request_format'] = $request->get( 'request_format' );
        $exact_date = $this->get_exact_date( $request );

        $view_args['no_navigation'] = $request->get( 'no_navigation' ) == true;

        // Find out which view of the calendar page was requested, and render it
        // accordingly.
        $view_args['action'] = $action;

        $view_args['request'] = $request;
        $view_args            = apply_filters(
            'ai1ec_view_args_array',
            $view_args
        );
        if ( null === $exact_date ) {
            $href = $this->_registry->get( 'html.element.href', $view_args )
                ->generate_href();
            return Ai1ec_Http_Response_Helper::redirect( $href, 307 );

        }
        return $view_args;
    }
}
