/* ----------------------------------------------------------------------------
 * Easy!Appointments - Open Source Web Scheduler
 *
 * @package     EasyAppointments
 * @author      A.Tselegidis <alextselegidis@gmail.com>
 * @copyright   Copyright (c) 2013 - 2016, Alex Tselegidis
 * @license     http://opensource.org/licenses/GPL-3.0 - GPLv3
 * @link        http://easyappointments.org
 * @since       v1.2.0
 * ---------------------------------------------------------------------------- */

window.BackendCalendarTableView = window.BackendCalendarTableView || {};

/**
 * Backend Calendar
 *
 * This module implements the table calendar view of backend.
 *
 * @module BackendCalendarTableView
 */
(function (exports) {
    'use strict';

    var $filterProvider;
    var $filterService;
    var lastFocusedEventData;

    /**
     * Bind page event handlers.
     */
    function bindEventHandlers() {
        var $calendarToolbar = $('#calendar-toolbar');
        var $calendar = $('#calendar');

        $calendar.on('click', '.calendar-header .btn.previous', function () {
            var dayInterval = $('#select-filter-item').val();
            var currentDate = $('.select-date').datepicker('getDate');
            var startDate = moment(currentDate).subtract(1, 'days');
            var endDate = startDate.clone().add(dayInterval - 1, 'days');
            $('.select-date').datepicker('setDate', startDate.toDate());
            createView(startDate.toDate(), endDate.toDate());
        });

        $calendar.on('click', '.calendar-header .btn.next', function () {
            var dayInterval = $('#select-filter-item').val();
            var currentDate = $('.select-date').datepicker('getDate');
            var startDate = moment(currentDate).add(1, 'days');
            var endDate = startDate.clone().add(dayInterval - 1, 'days');
            $('.select-date').datepicker('setDate', startDate.toDate());
            createView(startDate.toDate(), endDate.toDate());
        });

        $calendarToolbar.on('change', '#select-filter-item', function () {
            var dayInterval = $('#select-filter-item').val();
            var currentDate = $('.select-date').datepicker('getDate');
            var startDate = moment(currentDate);
            var endDate = startDate.clone().add(dayInterval - 1, 'days');
            createView(startDate.toDate(), endDate.toDate());
        });

        $calendarToolbar.on('click', '#reload-appointments', function () {
            // Remove all the events from the tables.
            $('.calendar-view .event').remove();

            // Fetch the events and place them in the existing HTML format.
            var dayInterval = $('#select-filter-item').val();
            var currentDate = $('.select-date').datepicker('getDate');
            var startDateMoment = moment(currentDate);
            var startDate = startDateMoment.toDate();
            var endDateMoment = startDateMoment.clone().add(dayInterval - 1, 'days');
            var endDate = endDateMoment.toDate();

            getCalendarEvents(startDate, endDate)
                .done(function (response) {
                    var currentDate = startDate;

                    while (currentDate <= endDate) {
                        $('.calendar-view .date-column').each(function (index, dateColumn) {
                            var $dateColumn = $(dateColumn);
                            var date = new Date($dateColumn.data('date'));

                            if (currentDate.getTime() !== date.getTime()) {
                                return true;
                            }

                            $dateColumn.find('.date-column-title').text(GeneralFunctions.formatDate(date, GlobalVariables.dateFormat));

                            $dateColumn.find('.provider-column').each(function (index, providerColumn) {
                                var $providerColumn = $(providerColumn);
                                var provider = $providerColumn.data('provider');

                                $providerColumn.find('.calendar-wrapper').fullCalendar('removeEvents');

                                createNonWorkingHours(
                                    $providerColumn.find('.calendar-wrapper'),
                                    $providerColumn.data('provider')
                                );

                                // Add the appointments to the column.
                                createAppointments($providerColumn, response.appointments);

                                // Add the unavailability events to the column.
                                createUnavailabilityEvents($providerColumn, response.unavailability_events);

                                // Add the provider breaks to the column.
                                var workingPlan = JSON.parse(provider.settings.working_plan);
                                var day = date.toString('dddd').toLowerCase();
                                if (workingPlan[day]) {
                                    var breaks = workingPlan[day].breaks;
                                    createBreaks($providerColumn, breaks);
                                }
                            });
                        });

                        currentDate.add({days: 1});
                    }

                    // setCalendarViewSize();
                    Backend.placeFooterToBottom();
                });
        });

        /**
         * Event: On Window Resize
         */
        $(window).on('resize', function () {
            setCalendarViewSize();
        });

        /**
         * Event: Popover Close Button "Click"
         *
         * Hides the open popover element.
         */
        $calendar.on('click', '.close-popover', function () {
            $(this).parents('.popover').popover('dispose');
        });

        /**
         * Event: Popover Edit Button "Click"
         *
         * Enables the edit dialog of the selected table event.
         */
        $calendar.on('click', '.edit-popover', function () {
            $(this).parents('.popover').popover('dispose');

            var $dialog;

            if (lastFocusedEventData.data.workingPlanException) {
                var date = lastFocusedEventData.data.date;
                var workingPlanException = lastFocusedEventData.data.workingPlanException;
                var provider = lastFocusedEventData.data.provider;

                WorkingPlanExceptionsModal
                    .edit(date, workingPlanException)
                    .done(function (date, workingPlanException) {
                        var successCallback = function () {
                            Backend.displayNotification(EALang.working_plan_exception_saved);

                            var workingPlanExceptions = JSON.parse(provider.settings.working_plan_exceptions) || {};

                            workingPlanExceptions[date] = workingPlanException;

                            for (var index in GlobalVariables.availableProviders) {
                                var availableProvider = GlobalVariables.availableProviders[index];

                                if (Number(availableProvider.id) === Number(provider.id)) {
                                    availableProvider.settings.working_plan_exceptions = JSON.stringify(workingPlanExceptions);
                                    break;
                                }
                            }

                            $('#select-filter-item').trigger('change'); // Update the calendar.
                        };

                        BackendCalendarApi.saveWorkingPlanException(date, workingPlanException, provider.id, successCallback, null);
                    });
            } else if (lastFocusedEventData.data.is_unavailable === '0') {
                var appointment = lastFocusedEventData.data;
                $dialog = $('#manage-appointment');

                BackendCalendarAppointmentsModal.resetAppointmentDialog();

                // Apply appointment data and show modal dialog.
                $dialog.find('.modal-header h3').text(EALang.edit_appointment_title);
                $dialog.find('#appointment-id').val(appointment.id);
                $dialog.find('#select-service').val(appointment.id_services).trigger('change');
                $dialog.find('#select-provider').val(appointment.id_users_provider);

                // Set the start and end datetime of the appointment.
                var startDatetime = Date.parseExact(appointment.start_datetime, 'yyyy-MM-dd HH:mm:ss');
                $dialog.find('#start-datetime').datetimepicker('setDate', startDatetime);

                var endDatetime = Date.parseExact(appointment.end_datetime, 'yyyy-MM-dd HH:mm:ss');
                $dialog.find('#end-datetime').datetimepicker('setDate', endDatetime);

                var customer = appointment.customer;
                $dialog.find('#customer-id').val(appointment.id_users_customer);
                $dialog.find('#first-name').val(customer.first_name);
                $dialog.find('#last-name').val(customer.last_name);
                $dialog.find('#email').val(customer.email);
                $dialog.find('#phone-number').val(customer.phone_number);
                $dialog.find('#address').val(customer.address);
                $dialog.find('#city').val(customer.city);
                $dialog.find('#zip-code').val(customer.zip_code);
                $dialog.find('#appointment-location').val(appointment.location);
                $dialog.find('#appointment-notes').val(appointment.notes);
                $dialog.find('#customer-notes').val(customer.notes);

                $dialog.modal('show');
            } else {
                var unavailable = lastFocusedEventData.data;

                // Replace string date values with actual date objects.
                unavailable.start_datetime = lastFocusedEventData.start.format('YYYY-MM-DD HH:mm:ss');
                var startDatetime = Date.parseExact(unavailable.start_datetime, 'yyyy-MM-dd HH:mm:ss');
                unavailable.end_datetime = lastFocusedEventData.end.format('YYYY-MM-DD HH:mm:ss');
                var endDatetime = Date.parseExact(unavailable.end_datetime, 'yyyy-MM-dd HH:mm:ss');

                $dialog = $('#manage-unavailable');
                BackendCalendarUnavailabilityEventsModal.resetUnavailableDialog();

                // Apply unavailable data to dialog.
                $dialog.find('.modal-header h3').text('Edit Unavailable Period');
                $dialog.find('#unavailable-start').datetimepicker('setDate', startDatetime);
                $dialog.find('#unavailable-id').val(unavailable.id);
                $dialog.find('#unavailable-provider').val(unavailable.id_users_provider);
                $dialog.find('#unavailable-end').datetimepicker('setDate', endDatetime);
                $dialog.find('#unavailable-notes').val(unavailable.notes);

                $dialog.modal('show');
            }
        });

        /**
         * Event: Popover Delete Button "Click"
         *
         * Displays a prompt on whether the user wants the appointment to be deleted. If he confirms the
         * deletion then an ajax call is made to the server and deletes the appointment from the database.
         */
        $calendar.on('click', '.delete-popover', function () {
            $(this).parents('.popover').popover('dispose'); // Hide the popover.

            var url;
            var data;

            // If id_role parameter exists the popover is an working plan exception.
            if (lastFocusedEventData.data.hasOwnProperty('id_roles')) {
                // Do not display confirmation prompt.
                url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_delete_working_plan_exception';

                data = {
                    csrfToken: GlobalVariables.csrfToken,
                    working_plan_exception: lastFocusedEventData.start.format('YYYY-MM-DD'),
                    provider_id: lastFocusedEventData.data.id
                };

                $.post(url, data)
                    .done(function () {
                        $('#message-box').dialog('close');

                        var workingPlanExceptions = JSON.parse(lastFocusedEventData.data.settings.working_plan_exceptions);
                        delete workingPlanExceptions[lastFocusedEventData.start.format('YYYY-MM-DD')];
                        lastFocusedEventData.data.settings.working_plan_exceptions = JSON.stringify(workingPlanExceptions);

                        // Refresh calendar event items.
                        $('#select-filter-item').trigger('change');
                    });
            } else if (lastFocusedEventData.data.is_unavailable === '0') {
                var buttons = [
                    {
                        text: EALang.cancel,
                        click: function () {
                            $('#message-box').dialog('close');
                        }
                    },
                    {
                        text: 'OK',
                        click: function () {
                            url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_delete_appointment';

                            data = {
                                csrfToken: GlobalVariables.csrfToken,
                                appointment_id: lastFocusedEventData.data.id,
                                delete_reason: $('#delete-reason').val()
                            };

                            $.post(url, data)
                                .done(function () {
                                    $('#message-box').dialog('close');

                                    // Refresh calendar event items.
                                    $('#select-filter-item').trigger('change');
                                });
                        }
                    }
                ];

                GeneralFunctions.displayMessageBox(EALang.delete_appointment_title,
                    EALang.write_appointment_removal_reason, buttons);

                $('<textarea/>', {
                    'class': 'form-control w-100',
                    'id': 'delete-reason',
                    'rows': '3'
                })
                    .appendTo('#message-box');
            } else {
                // Do not display confirmation prompt.
                url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_delete_unavailable';

                data = {
                    csrfToken: GlobalVariables.csrfToken,
                    unavailable_id: lastFocusedEventData.data.id
                };

                $.post(url, data)
                    .done(function () {
                        $('#message-box').dialog('close');

                        // Refresh calendar event items.
                        $('#select-filter-item').trigger('change');
                    });
            }
        });
    }

    /**
     * Create table view header container.
     *
     * The header contains the date navigation elements (buttons and datepicker).
     */
    function createHeader() {
        var $calendarFilter = $('#calendar-filter');

        $calendarFilter
            .find('select')
            .empty()
            .append(new Option('1 ' + EALang.day, 1))
            .append(new Option('3 ' + EALang.days, 3));

        var $calendarHeader = $('<div/>', {
            'class': 'calendar-header'
        })
            .appendTo('#calendar');

        $('<button/>', {
            'class': 'btn btn-xs btn-outline-secondary previous mr-2',
            'html': [
                $('<span/>', {
                    'class': 'fas fa-chevron-left'
                })
            ]
        })
            .appendTo($calendarHeader);

        $('<input/>', {
            'type': 'text',
            'class': 'form-control d-inline-block select-date mr-2',
            'value': GeneralFunctions.formatDate(new Date(), GlobalVariables.dateFormat, false)
        })
            .appendTo($calendarHeader);

        $('<button/>', {
            'class': 'btn btn-xs btn-outline-secondary next',
            'html': [
                $('<span/>', {
                    'class': 'fas fa-chevron-right'
                })
            ]
        })
            .appendTo($calendarHeader);

        var dateFormat;

        switch (GlobalVariables.dateFormat) {
            case 'DMY':
                dateFormat = 'dd/mm/yy';
                break;

            case 'MDY':
                dateFormat = 'mm/dd/yy';
                break;

            case 'YMD':
                dateFormat = 'yy/mm/dd';
                break;

            default:
                throw new Error('Invalid date format setting provided: ' + GlobalVariables.dateFormat);
        }

        $calendarHeader.find('.select-date').datepicker({
            defaultDate: new Date(),
            dateFormat: dateFormat,
            onSelect: function (dateText, instance) {
                var startDate = new Date(instance.currentYear, instance.currentMonth, instance.currentDay);
                var endDate = new Date(startDate.getTime()).add({days: parseInt($('#select-filter-item').val()) - 1});
                createView(startDate, endDate);
            }
        });

        var providers = GlobalVariables.availableProviders.filter(function (provider) {
            return GlobalVariables.user.role_slug === Backend.DB_SLUG_ADMIN
                || (GlobalVariables.user.role_slug === Backend.DB_SLUG_SECRETARY
                    && GlobalVariables.secretaryProviders.indexOf(provider.id) !== -1)
                || (GlobalVariables.user.role_slug === Backend.DB_SLUG_PROVIDER
                    && Number(provider.id) === Number(GlobalVariables.user.id));
        });

        // Create providers and service filters.
        $('<label/>', {
            'text': EALang.provider
        })
            .appendTo($calendarHeader);

        $filterProvider = $('<select/>', {
            'id': 'filter-provider',
            'multiple': 'multiple',
            'on': {
                'change': function () {
                    var startDate = new Date($('.calendar-view .date-column:first').data('date'));
                    var endDate = new Date(startDate.getTime()).add({days: parseInt($('#select-date').val()) - 1});
                    createView(startDate, endDate);
                }
            }
        })
            .appendTo($calendarHeader);

        if (GlobalVariables.user.role_slug !== Backend.DB_SLUG_PROVIDER) {
            providers.forEach(function (provider) {
                $filterProvider.append(new Option(provider.first_name + ' ' + provider.last_name, provider.id));
            });
        } else {
            providers.forEach(function (provider) {
                if (Number(provider.id) === Number(GlobalVariables.user.id)) {
                    $filterProvider.append(new Option(provider.first_name + ' ' + provider.last_name, provider.id));
                }
            });
        }

        $filterProvider.select2();

        var services = GlobalVariables.availableServices.filter(function (service) {
            var provider = providers.find(function (provider) {
                return provider.services.indexOf(service.id) !== -1;
            });

            return GlobalVariables.user.role_slug === Backend.DB_SLUG_ADMIN || provider;
        });

        $('<label/>', {
            'text': EALang.service
        })
            .appendTo($calendarHeader);

        $filterService = $('<select/>', {
            'id': 'filter-service',
            'multiple': 'multiple',
            'on': {
                'change': function () {
                    var startDate = new Date($('.calendar-view .date-column:first').data('date'));
                    var endDate = new Date(startDate.getTime()).add({days: parseInt($('#select-date').val()) - 1});
                    createView(startDate, endDate);
                }
            }
        })
            .appendTo($calendarHeader);

        services.forEach(function (service) {
            $filterService.append(new Option(service.name, service.id));
        });

        $filterService.select2();
    }

    /**
     * Create table schedule view.
     *
     * This method will destroy any previous instances and create a new view for displaying the appointments in
     * a table format.
     *
     * @param {Date} startDate Start date to be displayed.
     * @param {Date} endDate End date to be displayed.
     */
    function createView(startDate, endDate) {
        // Disable date navigation.
        $('#calendar .calendar-header .btn')
            .addClass('disabled')
            .prop('disabled', true);

        // Remember provider calendar view mode.
        var providerView = {};
        $('.provider-column').each(function (index, providerColumn) {
            var $providerColumn = $(providerColumn);
            var providerId = $providerColumn.data('provider').id;
            providerView[providerId] = $providerColumn.find('.calendar-wrapper').fullCalendar('getView').name;
        });

        $('#calendar .calendar-view').remove();

        Backend.placeFooterToBottom();

        var $calendarView = $('<div/>', {
            'class': 'calendar-view'
        })
            .appendTo('#calendar');

        $calendarView.data({
            startDate: startDate.toString('yyyy-MM-dd'),
            endDate: endDate.toString('yyyy-MM-dd')
        });

        var $wrapper = $('<div/>').appendTo($calendarView);

        getCalendarEvents(startDate, endDate)
            .done(function (response) {
                var currentDate = startDate;

                while (currentDate <= endDate) {
                    createDateColumn($wrapper, currentDate, response);
                    currentDate.add({days: 1});
                }

                setCalendarViewSize();

                Backend.placeFooterToBottom();

                // Activate calendar navigation.
                $('#calendar .calendar-header .btn').removeClass('disabled').prop('disabled', false);

                // Apply provider calendar view mode.
                $('.provider-column').each(function (index, providerColumn) {
                    var $providerColumn = $(providerColumn);
                    var providerId = $providerColumn.data('provider').id;
                    $providerColumn.find('.calendar-wrapper')
                        .fullCalendar('changeView', providerView[providerId] || 'agendaDay');
                });
            });
    }

    /**
     * Create Date Column Container
     *
     * This element will contain the provider columns.
     *
     * @param {jQuery} $wrapper The wrapper div element of the table view.
     * @param {Date} date Selected date for the column.
     * @param {Object[]} events Events to be displayed on this date.
     */
    function createDateColumn($wrapper, date, events) {
        var $dateColumn = $('<div/>', {
            'class': 'date-column'
        })
            .appendTo($wrapper);

        $dateColumn.data('date', date.getTime());

        $('<h5/>', {
            'class': 'date-column-title',
            'text': GeneralFunctions.formatDate(date, GlobalVariables.dateFormat)
        })
            .appendTo($dateColumn);

        var filterProviderIds = $filterProvider.val();
        var filterServiceIds = $filterService.val();

        var providers = GlobalVariables.availableProviders.filter(function (provider) {
            var servedServiceIds = provider.services.filter(function (serviceId) {
                var matches = filterServiceIds.filter(function (filterServiceId) {
                    return Number(serviceId) === Number(filterServiceId);
                });

                return matches.length;
            });

            return (!filterProviderIds.length && !filterServiceIds.length)
                || (filterProviderIds.length && !filterServiceIds.length && filterProviderIds.indexOf(provider.id) !== -1)
                || (!filterProviderIds.length && filterServiceIds.length && servedServiceIds.length)
                || (filterProviderIds.length && filterServiceIds.length && servedServiceIds.length && filterProviderIds.indexOf(provider.id) !== -1);
        });

        if (GlobalVariables.user.role_slug === 'provider') {
            GlobalVariables.availableProviders.forEach(function (provider) {
                if (Number(provider.id) === Number(GlobalVariables.user.id)) {
                    providers = [provider];
                }
            });
        }

        if (GlobalVariables.user.role_slug === 'secretary') {
            providers = [];
            GlobalVariables.availableProviders.forEach(function (provider) {
                if (GlobalVariables.secretaryProviders.indexOf(provider.id) > -1) {
                    providers.push(provider)
                }
            });
        }

        providers.forEach(function (provider) {
            createProviderColumn($dateColumn, date, provider, events);
        });
    }

    /**
     * Create Provider Column Container
     *
     * @param {jQuery} $dateColumn Element to container the provider's column.
     * @param {Date} date Selected date for the column.
     * @param {Object} provider Contains the provider data.
     * @param {Object[]} events Events to be displayed on this date.
     */
    function createProviderColumn($dateColumn, date, provider, events) {
        if (provider.services.length === 0) {
            return;
        }

        var $providerColumn = $('<div/>', {
            'class': 'provider-column'
        })
            .appendTo($dateColumn);

        $providerColumn.data('provider', provider);

        // Create calendar.
        createCalendar($providerColumn, date, provider);

        // Create non working hours.
        createNonWorkingHours(
            $providerColumn.find('.calendar-wrapper'),
            provider
        );

        // Add the appointments to the column.
        createAppointments($providerColumn, events.appointments);

        // Add the unavailability events to the column.
        createUnavailabilityEvents($providerColumn, events.unavailability_events);

        Backend.placeFooterToBottom();
    }

    /**
     * Get Calendar Component Height
     *
     * This method calculates the proper calendar height, in order to be displayed correctly, even when the browser
     * window is resizing.
     *
     * @return {Number} Returns the calendar element height in pixels.
     */
    function getCalendarHeight() {
        var result = window.innerHeight - $('#footer').outerHeight() - $('#header').outerHeight()
            - 60; // 60 for fine tuning
        return result > 500 ? result : 500; // Minimum height is 500px
    }

    function createCalendar($providerColumn, goToDate, provider) {
        var $wrapper = $('<div/>', {
            'class': 'calendar-wrapper'
        })
            .appendTo($providerColumn);

        var columnFormat = '';

        switch (GlobalVariables.dateFormat) {
            case 'DMY':
                columnFormat = 'ddd D/M';
                break;

            case 'MDY':
            case 'YMD':
                columnFormat = 'ddd M/D';
                break;

            default:
                throw new Error('Invalid date format setting provided!', GlobalVariables.dateFormat);
        }

        // Time formats
        var timeFormat = '';
        var slotTimeFormat = '';

        switch (GlobalVariables.timeFormat) {
            case 'military':
                timeFormat = 'H:mm';
                slotTimeFormat = 'H(:mm)';
                break;
            case 'regular':
                timeFormat = 'h:mm a';
                slotTimeFormat = 'h(:mm) a';
                break;
            default:
                throw new Error('Invalid time format setting provided!' + GlobalVariables.timeFormat);
        }

        var firstWeekday = GlobalVariables.firstWeekday;
        var firstWeekdayNumber = GeneralFunctions.getWeekDayId(firstWeekday);

        $wrapper.fullCalendar({
            defaultView: 'agendaDay',
            height: getCalendarHeight(),
            editable: true,
            timeFormat: timeFormat,
            slotLabelFormat: slotTimeFormat,
            allDaySlot: true,
            columnFormat: columnFormat,
            firstDay: firstWeekdayNumber,
            snapDuration: '00:15:00',
            header: {
                left: 'listDay,agendaDay',
                center: '',
                right: ''
            },
            // Selectable
            selectable: true,
            selectHelper: true,
            select: function (start, end, jsEvent) {
                if (!start.hasTime() || !end.hasTime()) {
                    return;
                }

                $('#insert-appointment').trigger('click');

                // Preselect service & provider.
                var $providerColumn = $(jsEvent.target).parents('.provider-column');
                var providerId = $providerColumn.data('provider').id;

                var provider = GlobalVariables.availableProviders.find(function (provider) {
                    return Number(provider.id) === Number(providerId);
                });

                var service = GlobalVariables.availableServices.find(function (service) {
                    return provider.services.indexOf(service.id) !== -1
                });

                if (service) {
                    $('#select-service').val(service.id);
                }

                if (!$('#select-service').val()) {
                    $('#select-service option:first').prop('selected', true);
                }

                $('#select-service').trigger('change');

                if (provider) {
                    $('#select-provider').val(provider.id);
                }

                if (!$('#select-provider').val()) {
                    $('#select-provider option:first').prop('selected', true);
                }

                $('#select-provider').trigger('change');

                // Preselect time
                $('#start-datetime').datepicker('setDate', new Date(start.format('YYY/MM/DD HH:mm:ss')));
                $('#end-datetime').datepicker('setDate', new Date(end.format('YYYY/MM/DD HH:mm:ss')));

                return false;
            },

            // Translations
            monthNames: [EALang.january, EALang.february, EALang.march, EALang.april,
                EALang.may, EALang.june, EALang.july, EALang.august,
                EALang.september, EALang.october, EALang.november,
                EALang.december],
            monthNamesShort: [EALang.january.substr(0, 3), EALang.february.substr(0, 3),
                EALang.march.substr(0, 3), EALang.april.substr(0, 3),
                EALang.may.substr(0, 3), EALang.june.substr(0, 3),
                EALang.july.substr(0, 3), EALang.august.substr(0, 3),
                EALang.september.substr(0, 3), EALang.october.substr(0, 3),
                EALang.november.substr(0, 3), EALang.december.substr(0, 3)],
            dayNames: [EALang.sunday, EALang.monday, EALang.tuesday, EALang.wednesday,
                EALang.thursday, EALang.friday, EALang.saturday],
            dayNamesShort: [EALang.sunday.substr(0, 3), EALang.monday.substr(0, 3),
                EALang.tuesday.substr(0, 3), EALang.wednesday.substr(0, 3),
                EALang.thursday.substr(0, 3), EALang.friday.substr(0, 3),
                EALang.saturday.substr(0, 3)],
            dayNamesMin: [EALang.sunday.substr(0, 2), EALang.monday.substr(0, 2),
                EALang.tuesday.substr(0, 2), EALang.wednesday.substr(0, 2),
                EALang.thursday.substr(0, 2), EALang.friday.substr(0, 2),
                EALang.saturday.substr(0, 2)],
            buttonText: {
                today: EALang.today,
                day: EALang.day,
                week: EALang.week,
                month: EALang.month,
                agendaDay: EALang.calendar,
                listDay: EALang.list,
            },

            // Calendar events need to be declared on initialization.
            eventClick: onEventClick,
            eventResize: onEventResize,
            eventDrop: onEventDrop,
            viewRender: onViewRender
        });

        $wrapper.fullCalendar('gotoDate', moment(goToDate));

        $('<h6/>', {
            'text': provider.first_name + ' ' + provider.last_name
        })
            .prependTo($providerColumn);
    }

    function onViewRender(view, element) {
        $(element).fullCalendar('option', 'height', getCalendarHeight());
    }

    function createNonWorkingHours($calendar, provider) {
        var workingPlan = JSON.parse(provider.settings.working_plan);
        var workingPlanExceptions = JSON.parse(provider.settings.working_plan_exceptions) || {};
        var view = $calendar.fullCalendar('getView');
        var start = view.start.clone();
        var end = view.end.clone();
        var selDayName = start.toDate().toString('dddd').toLowerCase();
        var selDayDate = start.format('YYYY-MM-DD');

        if (workingPlanExceptions[selDayDate]) {
            workingPlan[selDayName] = workingPlanExceptions[selDayDate];

            var workingPlanExceptionStart = selDayDate + ' ' + workingPlan[selDayName].start;
            var workingPlanExceptionEnd = selDayDate + ' ' + workingPlan[selDayName].end;

            var workingPlanExceptionEvent = {
                title: EALang.working_plan_exception,
                start: moment(workingPlanExceptionStart, 'YYYY-MM-DD HH:mm', true),
                end: moment(workingPlanExceptionEnd, 'YYYY-MM-DD HH:mm', true).add(1, 'day'),
                allDay: true,
                color: '#879DB4',
                editable: false,
                className: 'fc-working-plan-exception fc-custom',
                data: {
                    date: selDayDate,
                    workingPlanException: workingPlanExceptions[selDayDate],
                    provider: provider
                }
            };

            $calendar.fullCalendar('renderEvent', workingPlanExceptionEvent, false);
        }

        if (workingPlan[selDayName] === null) {
            var nonWorkingDay = {
                title: EALang.not_working,
                start: start,
                end: end,
                allDay: false,
                color: '#BEBEBE',
                editable: false,
                className: 'fc-unavailable'
            };

            $calendar.fullCalendar('renderEvent', nonWorkingDay, true);

            return;
        }

        var workDateStart = moment(start.toDate().toString('yyyy-MM-dd') + ' ' + workingPlan[selDayName].start);

        if (start < workDateStart) {
            unavailablePeriod = {
                title: EALang.not_working,
                start: start,
                end: workDateStart,
                allDay: false,
                color: '#BEBEBE',
                editable: false,
                className: 'fc-unavailable'
            };
            $calendar.fullCalendar('renderEvent', unavailablePeriod, false);
        }

        // Add unavailable period after work ends.
        var workDateEnd = moment(start.toDate().toString('yyyy-MM-dd') + ' ' + workingPlan[selDayName].end);

        if (end > workDateEnd) {
            var unavailablePeriod = {
                title: EALang.not_working,
                start: workDateEnd,
                end: end,
                allDay: false,
                color: '#BEBEBE',
                editable: false,
                className: 'fc-unavailable'
            };
            $calendar.fullCalendar('renderEvent', unavailablePeriod, false);
        }

        // Add unavailable periods for breaks.
        var breakStart;
        var breakEnd;

        workingPlan[selDayName].breaks.forEach(function (currentBreak) {
            breakStart = moment(start.toDate().toString('yyyy-MM-dd') + ' ' + currentBreak.start);
            breakEnd = moment(start.toDate().toString('yyyy-MM-dd') + ' ' + currentBreak.end);

            var unavailablePeriod = {
                title: EALang.break,
                start: breakStart,
                end: breakEnd,
                allDay: false,
                color: '#BEBEBE',
                editable: false,
                className: 'fc-unavailable fc-break'
            };

            $calendar.fullCalendar('renderEvent', unavailablePeriod, false);
        });
    }

    /**
     * Create Appointment Events
     *
     * This method will add the appointment events on the table view.
     *
     * @param {jQuery} $providerColumn The provider column container.
     * @param {Object[]} appointments Contains the appointment events data.
     */
    function createAppointments($providerColumn, appointments) {
        if (appointments.length === 0) {
            return;
        }

        var filterServiceIds = $filterService.val();

        appointments = appointments.filter(function (appointment) {
            return !filterServiceIds.length || filterServiceIds.indexOf(appointment.id_services) !== -1;
        });

        var calendarEvents = [];

        for (var index in appointments) {
            var appointment = appointments[index];

            if (appointment.id_users_provider !== $providerColumn.data('provider').id) {
                continue;
            }

            calendarEvents.push({
                id: appointment.id,
                title: appointment.service.name + ' - '
                    + appointment.customer.first_name + ' '
                    + appointment.customer.last_name,
                start: moment(appointment.start_datetime),
                end: moment(appointment.end_datetime),
                allDay: false,
                data: appointment // Store appointment data for later use.
            });
        }

        $providerColumn.find('.calendar-wrapper').fullCalendar('addEventSource', calendarEvents);
    }

    /**
     * Create Unavailability Events
     *
     * This method will add the unavailability events on the table view.
     *
     * @param {jQuery} $providerColumn The provider column container.
     * @param {Object[]} unavailabilityEvents Contains the unavailability events data.
     */
    function createUnavailabilityEvents($providerColumn, unavailabilityEvents) {
        if (unavailabilityEvents.length === 0) {
            return;
        }

        for (var index in unavailabilityEvents) {
            var unavailability = unavailabilityEvents[index];

            if (unavailability.id_users_provider !== $providerColumn.data('provider').id) {
                continue;
            }

            var event = {
                title: EALang.unavailable,
                start: moment(unavailability.start_datetime),
                end: moment(unavailability.end_datetime),
                allDay: false,
                color: '#879DB4',
                editable: true,
                className: 'fc-unavailable fc-custom',
                data: unavailability
            };

            $providerColumn.find('.calendar-wrapper').fullCalendar('renderEvent', event, false);
        }
    }

    /**
     * Create break events in the table view.
     *
     * @param {jQuery} $providerColumn The provider column container.
     * @param {Object[]} breaks Contains the break events data.
     */
    function createBreaks($providerColumn, breaks) {
        if (breaks.length === 0) {
            return;
        }

        var currentDate = new Date($providerColumn.parents('.date-column').data('date'));
        var $tbody = $providerColumn.find('table tbody');

        for (var index in breaks) {
            var entry = breaks[index];
            var startHour = entry.start.split(':');
            var eventDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate(), startHour[0], startHour[1]);
            var endHour = entry.end.split(':');
            var endDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate(), endHour[0], endHour[1]);
            var eventDuration = Math.round((endDate - eventDate) / 60000);
            var $event = $('<div/>', {
                'class': 'event unavailability break'
            });

            $event.html(
                EALang.break +
                ' <span class="hour">' + eventDate.toString('HH:mm') + '</span> (' + eventDuration + '\')');

            $event.data(entry);

            $tbody.find('tr').each(function (index, tr) {
                var $td = $(tr).find('td:first');

                var cellDate = new Date(currentDate.getTime()).set({
                    hour: parseInt($td.text().split(':')[0]),
                    minute: parseInt($td.text().split(':')[1])
                });

                if (eventDate < cellDate) {
                    // Remove the hour from the event if it is the same as the row.
                    if (eventDate.toString('HH:mm') === $(tr).prev().find('td').eq(0).text()) {
                        $event.find('.hour').remove();
                    }

                    $(tr).prev().find('td:gt(0)').each(function (index, td) {
                        $event.clone().appendTo($(td));
                    });
                    return false;
                }
            });
        }
    }

    /**
     * Get the event notes for the popup widget.
     *
     * @param {Event} event
     */
    function getEventNotes(event) {
        if (!event.data || !event.data.notes) {
            return '-';
        }

        var notes = event.data.notes;

        return notes.length > 100 ? notes.substring(0, 100) + '...' : notes;
    }

    /**
     * Calendar Event "Click" Callback
     *
     * When the user clicks on an appointment object on the calendar, then a data preview popover is display
     * above the calendar item.
     */
    function onEventClick(event, jsEvent) {
        $('.popover').popover('dispose'); // Close all open popovers.

        var $html;
        var displayEdit;
        var displayDelete;

        // Depending where the user clicked the event (title or empty space) we
        // need to use different selectors to reach the parent element.
        var $parent = $(jsEvent.target.offsetParent);
        var $altParent = $(jsEvent.target).parents().eq(1);

        if ($(this).hasClass('fc-unavailable') || $parent.hasClass('fc-unavailable') || $altParent.hasClass('fc-unavailable')) {
            displayEdit = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
                && GlobalVariables.user.privileges.appointments.edit === true)
                ? '' : 'd-none';
            displayDelete = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
                && GlobalVariables.user.privileges.appointments.delete === true)
                ? '' : 'd-none'; // Same value at the time.

            $html = $('<div/>', {
                'html': [
                    $('<strong/>', {
                        'text': EALang.start
                    }),
                    $('<span/>', {
                        'text': GeneralFunctions.formatDate(event.start.format('YYYY-MM-DD HH:mm:ss'), GlobalVariables.dateFormat, true)
                    }),
                    $('<br/>'),

                    $('<strong/>', {
                        'text': EALang.end
                    }),
                    $('<span/>', {
                        'text': GeneralFunctions.formatDate(event.end.format('YYYY-MM-DD HH:mm:ss'), GlobalVariables.dateFormat, true)
                    }),
                    $('<br/>'),

                    $('<strong/>', {
                        'text': EALang.notes
                    }),
                    $('<span/>', {
                        'text': getEventNotes(event)
                    }),
                    $('<br/>'),

                    $('<hr/>'),

                    $('<div/>', {
                        'class': 'd-flex justify-content-center',
                        'html': [
                            $('<button/>', {
                                'class': 'close-popover btn btn-outline-secondary mr-2',
                                'html': [
                                    $('<i/>', {
                                        'class': 'fas fa-ban mr-2'
                                    }),
                                    $('<span/>', {
                                        'text': EALang.close
                                    })
                                ]
                            }),
                            $('<button/>', {
                                'class': 'delete-popover btn btn-outline-secondary mr-2 ' + displayDelete,
                                'html': [
                                    $('<i/>', {
                                        'class': 'fas fa-trash-alt mr-2'
                                    }),
                                    $('<span/>', {
                                        'text': EALang.delete
                                    })
                                ]
                            }),
                            $('<button/>', {
                                'class': 'edit-popover btn btn-primary ' + displayEdit,
                                'html': [
                                    $('<i/>', {
                                        'class': 'fas fa-edit mr-2'
                                    }),
                                    $('<span/>', {
                                        'text': EALang.edit
                                    })
                                ]
                            }),
                        ]
                    })
                ]
            });
        } else if ($(this).hasClass('fc-working-plan-exception') || $parent.hasClass('fc-working-plan-exception') || $altParent.hasClass('fc-working-plan-exception')) {
            displayEdit = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
                && GlobalVariables.user.privileges.appointments.edit === true)
                ? '' : 'd-none'; // Same value at the time.

            displayDelete = (($parent.hasClass('fc-custom') || $altParent.hasClass('fc-custom'))
                && GlobalVariables.user.privileges.appointments.delete === true)
                ? '' : 'd-none'; // Same value at the time.

            $html = $('<div/>', {
                'html': [
                    $('<strong/>', {
                        'text': EALang.provider
                    }),
                    $('<span/>', {
                        'text': event.data ? event.data.provider.first_name + ' ' + event.data.provider.last_name : '-'
                    }),
                    $('<br/>'),

                    $('<strong/>', {
                        'text': EALang.start
                    }),
                    $('<span/>', {
                        'text': GeneralFunctions.formatDate(event.start.format('YYYY-MM-DD HH:mm:ss'), GlobalVariables.dateFormat, true)
                    }),
                    $('<br/>'),

                    $('<strong/>', {
                        'text': EALang.end
                    }),
                    $('<span/>', {
                        'text': GeneralFunctions.formatDate(event.end.format('YYYY-MM-DD HH:mm:ss'), GlobalVariables.dateFormat, true)
                    }),
                    $('<br/>'),

                    $('<strong/>', {
                        'text': EALang.timezone
                    }),
                    $('<span/>', {
                        'text': GlobalVariables.timezones[event.data.provider.timezone]
                    }),
                    $('<br/>'),

                    $('<hr/>'),

                    $('<div/>', {
                        'class': 'd-flex justify-content-center',
                        'html': [
                            $('<button/>', {
                                'class': 'close-popover btn btn-outline-secondary mr-2',
                                'html': [
                                    $('<i/>', {
                                        'class': 'fas fa-ban mr-2'
                                    }),
                                    $('<span/>', {
                                        'text': EALang.close
                                    })
                                ]
                            }),
                            $('<button/>', {
                                'class': 'delete-popover btn btn-outline-secondary mr-2 ' + displayDelete,
                                'html': [
                                    $('<i/>', {
                                        'class': 'fas fa-trash-alt mr-2'
                                    }),
                                    $('<span/>', {
                                        'text': EALang.delete
                                    })
                                ]
                            }),
                            $('<button/>', {
                                'class': 'edit-popover btn btn-primary ' + displayEdit,
                                'html': [
                                    $('<i/>', {
                                        'class': 'fas fa-edit mr-2'
                                    }),
                                    $('<span/>', {
                                        'text': EALang.edit
                                    })
                                ]
                            })
                        ]
                    })
                ]
            });
        } else {
            displayEdit = (GlobalVariables.user.privileges.appointments.edit === true)
                ? '' : 'd-none';
            displayDelete = (GlobalVariables.user.privileges.appointments.delete === true)
                ? '' : 'd-none';

            $html = $('<div/>', {
                'html': [
                    $('<strong/>', {
                        'text': EALang.start
                    }),
                    $('<span/>', {
                        'text': GeneralFunctions.formatDate(event.start.format('YYYY-MM-DD HH:mm:ss'), GlobalVariables.dateFormat, true)
                    }),
                    $('<br/>'),

                    $('<strong/>', {
                        'text': EALang.end
                    }),
                    $('<span/>', {
                        'text': GeneralFunctions.formatDate(event.end.format('YYYY-MM-DD HH:mm:ss'), GlobalVariables.dateFormat, true)
                    }),
                    $('<br/>'),

                    $('<strong/>', {
                        'text': EALang.timezone
                    }),
                    $('<span/>', {
                        'text': GlobalVariables.timezones[event.data.provider.timezone]
                    }),
                    $('<br/>'),

                    $('<strong/>', {
                        'text': EALang.service
                    }),
                    $('<span/>', {
                        'text': event.data.service.name
                    }),
                    $('<br/>'),

                    $('<strong/>', {
                        'text': EALang.provider
                    }),
                    GeneralFunctions.renderMapIcon(event.data.provider),
                    $('<span/>', {
                        'text': event.data.provider.first_name + ' ' + event.data.provider.last_name
                    }),
                    $('<br/>'),

                    $('<strong/>', {
                        'text': EALang.customer
                    }),
                    GeneralFunctions.renderMapIcon(event.data.customer),
                    $('<span/>', {
                        'text': event.data.customer.first_name + ' ' + event.data.customer.last_name
                    }),
                    $('<br/>'),

                    $('<strong/>', {
                        'text': EALang.email
                    }),
                    GeneralFunctions.renderMailIcon(event.data.customer.email),
                    $('<span/>', {
                        'text': event.data.customer.email
                    }),
                    $('<br/>'),

                    $('<strong/>', {
                        'text': EALang.phone
                    }),
                    GeneralFunctions.renderPhoneIcon(event.data.customer.phone_number),
                    $('<span/>', {
                        'text': event.data.customer.phone_number
                    }),
                    $('<br/>'),

                    $('<strong/>', {
                        'text': EALang.notes
                    }),
                    $('<span/>', {
                        'text': getEventNotes(event)
                    }),
                    $('<br/>'),

                    $('<hr/>'),

                    $('<div/>', {
                        'class': 'd-flex justify-content-center',
                        'html': [
                            $('<button/>', {
                                'class': 'close-popover btn btn-outline-secondary mr-2',
                                'html': [
                                    $('<i/>', {
                                        'class': 'fas fa-ban mr-2'
                                    }),
                                    $('<span/>', {
                                        'text': EALang.close
                                    })
                                ]
                            }),
                            $('<button/>', {
                                'class': 'delete-popover btn btn-outline-secondary mr-2' + displayDelete,
                                'html': [
                                    $('<i/>', {
                                        'class': 'fas fa-trash-alt mr-2'
                                    }),
                                    $('<span/>', {
                                        'text': EALang.delete
                                    })
                                ]
                            }),
                            $('<button/>', {
                                'class': 'edit-popover btn btn-primary ' + displayEdit,
                                'html': [
                                    $('<i/>', {
                                        'class': 'fas fa-edit mr-2'
                                    }),
                                    $('<span/>', {
                                        'text': EALang.edit
                                    })
                                ]
                            })
                        ]
                    })
                ]
            });
        }

        $(jsEvent.target).popover({
            placement: 'top',
            title: event.title,
            content: $html,
            html: true,
            container: '#calendar',
            trigger: 'manual'
        });

        lastFocusedEventData = event;

        $(jsEvent.target).popover('toggle');

        // Fix popover position.
        if ($('.popover').length > 0 && $('.popover').position().top < 200) {
            $('.popover').css('top', '200px');
        }
    }

    /**
     * Calendar Event "Resize" Callback
     *
     * The user can change the duration of an event by resizing an appointment object on the calendar. This
     * change needs to be stored to the database too and this is done via an ajax call.
     *
     * @see updateAppointmentData()
     */
    function onEventResize(event, delta, revertFunc) {
        if (GlobalVariables.user.privileges.appointments.edit === false) {
            revertFunc();
            Backend.displayNotification(EALang.no_privileges_edit_appointments);
            return;
        }

        var $calendar = $('#calendar');

        if ($('#notification').is(':visible')) {
            $('#notification').hide('bind');
        }

        var successCallback;

        if (event.data.is_unavailable === '0') {
            // Prepare appointment data.
            event.data.end_datetime = Date.parseExact(
                event.data.end_datetime, 'yyyy-MM-dd HH:mm:ss')
                .add({days: delta.days(), hours: delta.hours(), minutes: delta.minutes()})
                .toString('yyyy-MM-dd HH:mm:ss');

            var appointment = GeneralFunctions.clone(event.data);

            // Must delete the following because only appointment data should be provided to the AJAX call.
            delete appointment.customer;
            delete appointment.provider;
            delete appointment.service;

            // Success callback
            successCallback = function () {
                // Display success notification to user.
                var undoFunction = function () {
                    appointment.end_datetime = event.data.end_datetime = Date.parseExact(
                        appointment.end_datetime, 'yyyy-MM-dd HH:mm:ss')
                        .add({days: -delta.days(), hours: -delta.hours(), minutes: -delta.minutes()})
                        .toString('yyyy-MM-dd HH:mm:ss');

                    var url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_save_appointment';

                    var data = {
                        csrfToken: GlobalVariables.csrfToken,
                        appointment_data: JSON.stringify(appointment)
                    };

                    $.post(url, data)
                        .done(function () {
                            $('#notification').hide('blind');
                        });

                    revertFunc();
                };

                Backend.displayNotification(EALang.appointment_updated, [
                    {
                        'label': EALang.undo,
                        'function': undoFunction
                    }
                ]);
                $('#footer').css('position', 'static'); // Footer position fix.

                // Update the event data for later use.
                $calendar.fullCalendar('updateEvent', event);
            };

            // Update appointment data.
            BackendCalendarApi.saveAppointment(appointment, null, successCallback);
        } else {
            // Update unavailable time period.
            var unavailable = {
                id: event.data.id,
                start_datetime: event.start.format('YYYY-MM-DD HH:mm:ss'),
                end_datetime: event.end.format('YYYY-MM-DD HH:mm:ss'),
                id_users_provider: event.data.id_users_provider
            };

            event.data.end_datetime = unavailable.end_datetime;

            // Define success callback function.
            successCallback = function () {
                // Display success notification to user.
                var undoFunction = function () {
                    unavailable.end_datetime = event.data.end_datetime = Date.parseExact(
                        unavailable.end_datetime, 'yyyy-MM-dd HH:mm:ss')
                        .add({minutes: -delta.minutes()})
                        .toString('yyyy-MM-dd HH:mm:ss');

                    var url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_save_unavailable';

                    var data = {
                        csrfToken: GlobalVariables.csrfToken,
                        unavailable: JSON.stringify(unavailable)
                    };

                    $.post(url, data)
                        .done(function () {
                            $('#notification').hide('blind');
                        });

                    revertFunc();
                };

                Backend.displayNotification(EALang.unavailable_updated, [
                    {
                        'label': EALang.undo,
                        'function': undoFunction
                    }
                ]);

                $('#footer').css('position', 'static'); // Footer position fix.

                // Update the event data for later use.
                $calendar.fullCalendar('updateEvent', event);
            };

            BackendCalendarApi.saveUnavailable(unavailable, successCallback);
        }
    }

    /**
     * Calendar Event "Drop" Callback
     *
     * This event handler is triggered whenever the user drags and drops an event into a different position
     * on the calendar. We need to update the database with this change. This is done via an ajax call.
     */
    function onEventDrop(event, delta, revertFunc) {
        if (GlobalVariables.user.privileges.appointments.edit === false) {
            revertFunc();
            Backend.displayNotification(EALang.no_privileges_edit_appointments);
            return;
        }

        if ($('#notification').is(':visible')) {
            $('#notification').hide('bind');
        }

        var successCallback;

        if (event.data.is_unavailable === '0') {
            // Prepare appointment data.
            var appointment = GeneralFunctions.clone(event.data);

            // Must delete the following because only appointment data should be provided to the ajax call.
            delete appointment.customer;
            delete appointment.provider;
            delete appointment.service;

            appointment.start_datetime = Date.parseExact(
                appointment.start_datetime, 'yyyy-MM-dd HH:mm:ss')
                .add({days: delta.days(), hours: delta.hours(), minutes: delta.minutes()})
                .toString('yyyy-MM-dd HH:mm:ss');

            appointment.end_datetime = Date.parseExact(
                appointment.end_datetime, 'yyyy-MM-dd HH:mm:ss')
                .add({days: delta.days(), hours: delta.hours(), minutes: delta.minutes()})
                .toString('yyyy-MM-dd HH:mm:ss');

            event.data.start_datetime = appointment.start_datetime;
            event.data.end_datetime = appointment.end_datetime;

            // Define success callback function.
            successCallback = function () {
                // Define the undo function, if the user needs to reset the last change.
                var undoFunction = function () {
                    appointment.start_datetime = Date.parseExact(
                        appointment.start_datetime, 'yyyy-MM-dd HH:mm:ss')
                        .add({days: -delta.days(), hours: -delta.hours(), minutes: -delta.minutes()})
                        .toString('yyyy-MM-dd HH:mm:ss');

                    appointment.end_datetime = Date.parseExact(
                        appointment.end_datetime, 'yyyy-MM-dd HH:mm:ss')
                        .add({days: -delta.days(), hours: -delta.hours(), minutes: -delta.minutes()})
                        .toString('yyyy-MM-dd HH:mm:ss');

                    event.data.start_datetime = appointment.start_datetime;
                    event.data.end_datetime = appointment.end_datetime;

                    var url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_save_appointment';

                    var data = {
                        csrfToken: GlobalVariables.csrfToken,
                        appointment_data: JSON.stringify(appointment)
                    };

                    $.post(url, data)
                        .done(function () {
                            $('#notification').hide('blind');
                        });

                    revertFunc();
                };

                Backend.displayNotification(EALang.appointment_updated, [
                    {
                        'label': EALang.undo,
                        'function': undoFunction
                    }
                ]);

                $('#footer').css('position', 'static'); // Footer position fix.
            };

            // Update appointment data.
            BackendCalendarApi.saveAppointment(appointment, null, successCallback);
        } else {
            // Update unavailable time period.
            var unavailable = {
                id: event.data.id,
                start_datetime: event.start.format('YYYY-MM-DD HH:mm:ss'),
                end_datetime: event.end.format('YYYY-MM-DD HH:mm:ss'),
                id_users_provider: event.data.id_users_provider
            };

            successCallback = function () {
                var undoFunction = function () {
                    unavailable.start_datetime = Date.parseExact(
                        unavailable.start_datetime, 'yyyy-MM-dd HH:mm:ss')
                        .add({days: -delta.days(), minutes: -delta.minutes()})
                        .toString('yyyy-MM-dd HH:mm:ss');

                    unavailable.end_datetime = Date.parseExact(
                        unavailable.end_datetime, 'yyyy-MM-dd HH:mm:ss')
                        .add({days: -delta.days(), minutes: -delta.minutes()})
                        .toString('yyyy-MM-dd HH:mm:ss');

                    event.data.start_datetime = unavailable.start_datetime;
                    event.data.end_datetime = unavailable.end_datetime;

                    var url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_save_unavailable';

                    var data = {
                        csrfToken: GlobalVariables.csrfToken,
                        unavailable: JSON.stringify(unavailable)
                    };

                    $.post(url, data)
                        .done(function () {
                            $('#notification').hide('blind');
                        });

                    revertFunc();
                };

                Backend.displayNotification(EALang.unavailable_updated, [
                    {
                        label: EALang.undo,
                        function: undoFunction
                    }
                ]);

                $('#footer').css('position', 'static'); // Footer position fix.
            };

            BackendCalendarApi.saveUnavailable(unavailable, successCallback);
        }
    }

    /**
     * Set Table Calendar View
     *
     * This method will set the optimal size in the calendar view elements in order to fit in the page without
     * using scrollbars.
     */
    function setCalendarViewSize() {
        var height = window.innerHeight - $('#header').outerHeight() - $('#footer').outerHeight()
            - $('#calendar-toolbar').outerHeight() - $('.calendar-header').outerHeight() - 50;

        if (height < 500) {
            height = 500;
        }

        var $dateColumn = $('.date-column');
        var $calendarViewDiv = $('.calendar-view > div');

        $calendarViewDiv.css('min-width', '1000%');

        var width = 0;

        $dateColumn.each(function (index, dateColumn) {
            width += $(dateColumn).outerWidth();
        });

        $calendarViewDiv.css('min-width', width + 200);

        var dateColumnHeight = $dateColumn.outerHeight();

        $('.calendar-view .not-working').outerHeight((dateColumnHeight > height ? dateColumnHeight : height) - 70);
    }

    /**
     * Get the calendar events.
     *
     * @param {Date} startDate The start date of the selected period.
     * @param {Date} endDate The end date of the selected period.
     *
     * @return {jQuery.jqXHR}
     */
    function getCalendarEvents(startDate, endDate) {
        var url = GlobalVariables.baseUrl + '/index.php/backend_api/ajax_get_calendar_events';

        var data = {
            csrfToken: GlobalVariables.csrfToken,
            startDate: startDate.toString('yyyy-MM-dd'),
            endDate: endDate.toString('yyyy-MM-dd')
        };

        return $.ajax({
            url: url,
            data: data,
            method: 'POST',
            beforeSend: function () {
                // $('#loading').css('visibility', 'hidden');
            },
            complete: function () {
                // $('#loading').css('visibility', '');
            }
        });
    }

    /**
     * Initialize Page
     */
    exports.initialize = function () {
        createHeader();

        var startDate = moment().toDate();
        var endDate = moment().add(Number($('#select-filter-item').val()) - 1, 'days').toDate();

        createView(startDate, endDate);

        $('#insert-working-plan-exception').hide();

        bindEventHandlers();

        // Hide Google Calendar Sync buttons cause they can not be used within this view.
        $('#enable-sync, #google-sync').hide();
    };
})(window.BackendCalendarTableView);