$(function () {
    /**
     * Sticky header
     */
    function stickyHeader() {
        if (sticky.offset().top > scrollTop) {
            sticky.addClass('site-header--stuck');
            $('body').addClass('header-stuck');
        } else {
            sticky.removeClass('site-header--stuck');
            $('body').removeClass('header-stuck');
        }
    }

    if ($(window).width() > 992 && $('.site-header').length > 0) {
        var sticky = $('.site-header'),
            scrollTop = sticky.height();

        stickyHeader();

        $(window).scroll(function () {
            stickyHeader();
        });
    }

    /**
     * Tooltips
     */
    if ($('[data-toggle="tooltip"]').length > 0) {
        $('[data-toggle="tooltip"]').tooltip({
            html: "true"
        });
    }



    /**
     *  Table settings
     */
    if ($('.js-table-settings').length > 0) {
        $('.js-table-settings-link').on('click', function (e) {
            $(this)
                .parent().toggleClass('open')
                .parent().parent().next().toggleClass('open');
            e.preventDefault();
        });
    }

    /**
     *  Navigation with selector for tabs
     */
    if ($('.js-tab-select-navigation').length > 0) {
        $('.js-tab-select-navigation').on('change', function (e) {
            var target = $(this).val(),
                tabContent = $(this).data("content");
            $(tabContent).find('.tab-pane').removeClass('active in');
            $(target).addClass('active in');
        });
    }

    /**
     *  Navigation lateral with selector
     */
    if ($('.js-link-select-navigation').length > 0) {
        $('.js-link-select-navigation select').on('change', function (e) {
            if ($(this).val() !== '') {
                window.location.href = $(this).val();
            }
        });
    }


    /**
     * Notifications
     */
    var notifications = $('#notifications');
    if (notifications.length > 0) {
        notifications.appendTo('.notifications-placeholder');
        notifications.show();
    }

});

/**
 * App main class.
 */
function App() {

    /** 
     * Shows a message using the preloaded #modal-message HTML
     */
    this.showMessage = function (message, description, alert) {
        var deferred = $.Deferred();

        $('#close-button').show();
        $('#prompt-buttons').hide();
        $('#modal-message-text').html(message);
        if (description)
            $('#modal-message-description').html(description);
        else
            $('#modal-message-description').html('');

        $('#modal-message').modal({ backdrop: 'static', keyboard: false });

        if (alert)
            $('#modal-message-text').addClass('alert alert--danger');
        else
            $('#modal-message-text').removeClass('alert alert--danger');

        //Handle button clicks and resolve promises
        $('#close-button').off();
        $('#close-button').on('click', function () {
            $('#modal-message').modal('hide');
            deferred.resolve(true);
        });

        $('#modal-message').off();
        $('#modal-message').on('hidden.bs.modal', function () {
            deferred.resolve(true);
        });

        return deferred.promise();
    }

    this.showErrorMessage = function (message, description) {
        return this.showMessage(message, description, true);
    }

    this.scrollToAnchorElement = function (anchorId) {
        var safeHeaderHeight = $('.site-header').height() + $('.navbar-sec').height() + 73;
        $div = $('#' + anchorId);
        $('html, body').stop().animate({
            scrollTop: $div.offset().top - safeHeaderHeight
        }, 800);
    }

    /** 
     * Shows a question with a YES/NO answer using the preloaded #modal-message HTML
     */
    this.showQuestion = function (message, description) {
        var deferred = $.Deferred();
        $('#close-button').hide();
        $('#prompt-buttons').show();
        $('#modal-message-text').html(message);
        $('#modal-message-description').html(description);
        $('#modal-message').modal({ backdrop: 'static', keyboard: false });

        //Handle button clicks and resolve promises
        $('#yes-button, #no-button, #modal-message').off();
        $('#yes-button').on('click', function () {
            $('#modal-message').modal('hide');
            deferred.resolve(true);
        });
        $('#no-button').on('click', function () {
            $('#modal-message').modal('hide');
            deferred.resolve(false);
        });


        return deferred.promise();
    }

    this.isMobile = function () {
        return (/Android|webOS|iPhone|iPad|iPod|Opera Mini/i.test(navigator.userAgent));
    }

    this.copyToClipboard = function (text) {
        if (window.clipboardData && window.clipboardData.setData) {
            // IE specific code path to prevent textarea being shown while dialog is visible.
            return clipboardData.setData("Text", text);

        } else if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
            var textarea = document.createElement("textarea");
            textarea.textContent = text;
            textarea.style.position = "fixed";  // Prevent scrolling to bottom of page in MS Edge.
            document.body.appendChild(textarea);
            textarea.select();
            try {
                return document.execCommand("copy");  // Security exception may be thrown by some browsers.
            } catch (ex) {
                console.warn("Copy to clipboard failed.", ex);
                return false;
            } finally {
                document.body.removeChild(textarea);
            }
        }
    }
}

/**
 * Manages the registration process
 */

function SignupManager(settings) {
    var self = this;
    var errorInForm = settings.errorInForm;

    var SignupViewModel = function () {
        var self = this;

        settings.coworker.Avatar = null;
        settings.coworker.TourDate = null;
        settings.coworker.hasBillingDetails = false;
        settings.coworker.isPayingMember = false;
        settings.coworker.ProfileTagsArray = settings.coworker.ProfileTags != null ? (settings.coworker.ProfileTags + '').split(',') : [];

        //Build custom field helpers
        _.forEach(settings.customFields, function (field) {
            if (field.AvailableOptions)
                field.AvailableOptionsArray = field.AvailableOptions.split(',');

            if (field.FieldType === 'Dropdown') {
                if (settings.coworker['Custom' + field.CustomFieldIndex])
                    settings.coworker['Custom' + field.CustomFieldIndex + 'Array'] = settings.coworker['Custom' + field.CustomFieldIndex].split(',');
                else
                    settings.coworker['Custom' + field.CustomFieldIndex + 'Array'] = [];
            }
        });

        self.coworker = ko.mapping.fromJS(settings.coworker);
        self.profileTags = ko.mapping.fromJS(settings.profileTags);
        self.customFields = _.toArray(_.groupBy(settings.customFields, 'GroupName'));
        self.busy = ko.observable(false);
        self.alreadyRegistered = ko.observable(false);
        self.team = {
            Name: ko.observable(),
            Description: ko.observable(),
        };

        self.datePickerOptions = {
            today: '',
            clear: '',
            selectYears: 200,
            selectMonths: true,
            close: 'Chiudi',
            format: 'dddd, dd mmmm yyyy'
        };
        this.timePickerOptions = {
            today: '',
            clear: '',
            close: 'Chiudi',
        };



        /**
         * Validates and submits registration data
         */
        self.submit = function () {

            if (ko.validatedObservable) {
                var validatedvm = ko.validatedObservable(vm.coworker);
                if (validatedvm.isValid() === false) {
                    vm.coworker.errors.showAllMessages(true);
                    app.showErrorMessage(errorInForm);
                    return false;
                }
            }

            //Process values for custom fields 
            for (var i = 1; i <= 30; i++) {
                //Convert arrays into comma separated strings.
                var prop = ko.unwrap(vm.coworker['Custom' + i + 'Array']);
                if (prop)
                    vm.coworker['Custom' + i] = prop.join(',');

                //Make sure boolean values are submitted as true
                if (ko.unwrap(vm.coworker['Custom' + i]) === 'True')
                    vm.coworker['Custom' + i]('true');
            }

            var data = {
                base64avatar: vm.coworker.Avatar,
                Coworker: vm.coworker,
                Team: vm.team,
                recaptcha: $('#g-recaptcha-response').val()
            };

            var jsData = ko.mapping.toJS(data)
            //Tour Date
            if (vm.coworker.TourDate())
                jsData.Coworker.TourDate = moment(vm.coworker.TourDate()).format('YYYY-MM-DDT') + moment(vm.coworker.TourDate()).format('HH:mm') + 'Z';

            vm.busy(true);

            $.ajax({
                url: settings.url,
                type: 'POST',
                data: ko.mapping.toJSON(jsData),
                cache: false,
                contentType: "application/json",
            })
                .done(function (result) {
                    if (result.SuccessMessage)
                        app.showMessage(settings.successMessage || result.SuccessMessage);
                    else if (result.RedirectTo)
                        window.location = result.RedirectTo;
                    else
                        app.showErrorMessage(result.ErrorMessage || settings.errorMessage);

                    vm.busy(false);
                })
                .fail(function () {
                    app.showMessage(settings.errorMessage);
                    vm.busy(false);
                });

            return false;
        }
    }

    var vm = new SignupViewModel();

    vm.coworker.Email.subscribe(function (value) {
        $.get('/en/user/exists?email=' + value, function (result) {
            if (result) {
                vm.alreadyRegistered(true);
            }
            else {
                vm.alreadyRegistered(false);
            }
        })
    })

    self.init = function () {
        applyBindings();
    }

    function applyBindings() {
        //Valdidation
        ko.validation.init({
            registerExtenders: true,
            messagesOnModified: true,
            insertMessages: false,
            parseInputAttributes: true,
            decorateInputElement: true
        }, true);
        vm.errors = ko.validation.group(vm, { deep: true });
        vm.coworker.errors = ko.validation.group(vm.coworker, { deep: true });

        ko.applyBindings(vm);
    }
}

/**
 * Manages the UI of the ticket purchase process
 */
function TicketManager(settings) {
    var self = this;
    var currentLang = settings.currentLang;
    var availableCredit = settings.availableCredit;
    var maxQuantity = settings.maxQuantity;
    var fullName = settings.fullName;
    var email = settings.email;
    var errorInForm = settings.errorInForm;
    var attendeeCache = [];
    var eventId = settings.eventId;
    var productId = settings.productId;

    for (var k = 0; k < maxQuantity * 100; k++) {
        attendeeCache.push({
            productId: ko.observable(),
            productName: ko.observable(),
            fullName: ko.observable(),
            email: ko.observable()
        });
    }

    var StoreViewModel = function () {

        var self = this;
        var doPurchaseEnabled = ko.observable(true);
        var products = ko.observableArray();
        var qtys = ko.observableArray();
        var attendees = ko.observableArray([]);


        var coworker = {
            fullName: ko.observable(),

            salutation: ko.observable(),
            email: ko.observable(),
            companyName: ko.observable(),
            password: ko.observable(),
            cityName: ko.observable(),
            postCode: ko.observable(),
            mobilePhone: ko.observable(),
            state: ko.observable(),
            address: ko.observable(),
            createUser: ko.observable(true),

            billingName: ko.observable(),
            billingAddress: ko.observable(),
            billingState: ko.observable(),
            billingCityName: ko.observable(),
            billingPostCode: ko.observable(),
            taxIDNumber: ko.observable(),
        }

        this.discountCode = ko.observable('');
        this.discountCodeInfo = ko.observable();
        this.attendees = attendees;
        this.coworker = coworker;
        this.products = products;
        this.qtys = qtys;
        this.doPurchaseEnabled = doPurchaseEnabled;
        this.availableCredit = ko.observable(availableCredit);
        this.formError = ko.observable();
        this.purchaseError = ko.observable();
        this.purchaseMessage = ko.observable();
        this.alreadyRegistered = ko.observable(false);
        this.canPurchase = ko.observable(email != '');
    };

    var vm = new StoreViewModel();

    vm.coworker.email.subscribe(function (value) {
        $.get('/en/user/exists?email=' + value, function (result) {
            if (result) {
                vm.alreadyRegistered(true);
                vm.formError('Risulti già registrato, effettua il login.');
                vm.canPurchase(false);
            }
            else {
                vm.formError(null);
                vm.alreadyRegistered(false);
                vm.canPurchase(vm.email() != '');
            }
        })
    })
    /**
     * Loads the tickets for this event
     */
    this.loadData = function () {
        $.when($.post('/' + currentLang + '/events/productsjson?eventId=' + eventId + '&productId=' + productId))
            .then(mapData)
            .then(applyBindings);


        function mapData(data) {
            vm.products = ko.mapping.fromJS(data.products);
            vm.qtys = ko.observableArray([1, 2, 3, 4, 5]);

            var ps = vm.products();
            for (var i = 0; i < ps.length; i++) {
                ps[i].qtys = ko.observableArray();
                var absoluteMax = ps[i].MaxTicketsPerAttendee() || maxQuantity || 10;
                var max = (ps[i].TicketsLeft() >= absoluteMax ? absoluteMax : ps[i].TicketsLeft());
                if (max > maxQuantity)
                    max = maxQuantity;

                for (var j = 1; j <= max; j++) {
                    ps[i].qtys.push(j);
                }

            }


            vm.totalOneOff = ko.computed(function () {
                var ps = vm.products();
                var total = 0;
                for (var i = 0; i < ps.length; i++) {
                    if (ps[i].Quantity())
                        total = total + (ps[i].Price() * parseInt(ps[i].Quantity()));
                }

                total = total - availableCredit;
                if (total < 0) total = 0;
                return total.toFixed(2);
            }, this);

            vm.selectedProducts = ko.computed(function () {
                return ko.utils.arrayFilter(vm.products(), function (item) {
                    return item.Quantity() > 0;
                });
            }, this);

            vm.validateRegistration = function () {
                vm.formError(null);
                vm.purchaseError(null);
                vm.purchaseMessage(null);
                vm.canPurchase(false);

                if (vm.coworker.errors().length > 0) {
                    vm.coworker.errors.showAllMessages();
                    vm.formError('Please complete or correct the registration form');
                    return;
                }

                location.hash = '';
                location.hash = '#purchase';
                vm.canPurchase(true);
                return true;
            };

            vm.doPurchase = function () {

                vm.purchaseError(null);
                if (ko.validatedObservable) {
                    var validatedvm = ko.validatedObservable(vm);
                    if (validatedvm.isValid() === false) {
                        vm.errors.showAllMessages(true);
                        app.showErrorMessage(errorInForm);
                        return;
                    }
                }

                if (!vm.doPurchaseEnabled()) return;
                vm.doPurchaseEnabled(false);

                var plainJs = {
                    discountCode: vm.discountCode(),
                    redirectUrl: '/' + currentLang + '/events/payment',
                    products: ko.toJS(vm.selectedProducts),
                    coworker: ko.toJS(vm.coworker),
                    attendees: ko.toJS(vm.attendees)
                };
                $.when(sendData(plainJs))
                    .then(processPurchaseResult);

                function sendData(d) {
                    return $.ajax({
                        type: "POST",
                        url: '/' + currentLang + '/events/purchase',
                        contentType: "application/json",
                        dataType: "json",
                        data: JSON.stringify(d)
                    });
                }

                function processPurchaseResult(result) {
                    if (result.WasSuccessful) {
                        if (result.Redirect)
                            window.location = result.Redirect;
                        else
                            vm.purchaseMessage(result.Message);
                    } else {
                        vm.doPurchaseEnabled(true);
                        vm.purchaseError(result.Message);
                    }
                }
            };

        }

        function applyBindings() {

            //Valdidation
            ko.validation.init({
                registerExtenders: true,
                messagesOnModified: true,
                insertMessages: false,
                parseInputAttributes: true,
                decorateInputElement: true
            }, true);

            vm.errors = ko.validation.group(vm, { deep: true });
            vm.coworker.errors = ko.validation.group(vm.coworker, { deep: true });

            ko.applyBindings(vm);

            vm.selectedProducts.subscribe(function () {
                vm.attendees.removeAll();


                for (var i = 0; i < vm.selectedProducts().length; i++) {
                    for (var j = 0; j < vm.selectedProducts()[i].Quantity(); j++) {

                        var c = attendeeCache[(i * 10) + j];

                        c.productId(vm.selectedProducts()[i].Id());
                        c.productName(vm.selectedProducts()[i].Name());

                        if (c.fullName() == null) {
                            c.fullName(vm.coworker.fullName() || fullName);
                            c.email(vm.coworker.email() || email);
                        }
                        vm.attendees.push(c);
                    }
                }

                return vm.attendees;
            });
        }
    }

    function updateProductPrices() {
        $.when($.post('/' + currentLang + '/events/productsjson?discountCode=' + vm.discountCode() + '&eventId=' + eventId + '&productId=' + productId))
            .then(mapData);

        function mapData(data) {
            vm.discountCodeInfo(data.discountCode);
            var ps = vm.products();
            for (var i = 0; i < ps.length; i++) {
                var foundProduct = _.find(data.products, { Id: ps[i].Id() });
                ps[i].Price(foundProduct.Price)
                ps[i].PriceFormatted(foundProduct.PriceFormatted)
            }
        }
    }

    var updatedPricesDb = _.debounce(updateProductPrices, 1000);
    vm.discountCode.subscribe(updatedPricesDb);
}

/**
 * Manages the product purchase process 
 */
function StoreManager(settings) {
    var self = this;
    var currentLang = settings.lang || 'en';
    var purchased = false;

    var StoreViewModel = function () {

        var self = this;
        var doPurchaseEnabled = ko.observable(true);
        var products = ko.observableArray();
        var qtys = ko.observableArray();

        this.discountCode = ko.observable('');
        this.discountCodeInfo = ko.observable();
        this.products = products;
        this.qtys = qtys;
        this.doPurchaseEnabled = doPurchaseEnabled;
    };

    var vm = new StoreViewModel();

    function updateProductPrices() {
        $.when($.post('/' + currentLang + '/store/productsjson?discountCode=' + vm.discountCode() + '&onlyTimePasses=' + settings.onlyTimePasses + '&productId=' + settings.productId + '&tag=' + settings.tag))
            .then(mapData);

        function mapData(data) {
            vm.discountCodeInfo(data.discountCode);
            var ps = vm.products();
            for (var i = 0; i < ps.length; i++) {
                var foundProduct = _.find(data.products, { Id: ps[i].Id() });
                ps[i].Price(foundProduct.Price)
                ps[i].PriceFormatted(foundProduct.PriceFormatted)
            }
        }
    }

    function loadData() {

        $.when($.post('/' + currentLang + '/store/productsjson?discountCode=' + vm.discountCode() + '&onlyTimePasses=' + settings.onlyTimePasses + '&productId=' + settings.productId + '&tag=' + settings.tag))
            .then(mapData)
            .then(applyBindings);


        function mapData(data) {

            vm.products = ko.mapping.fromJS(data.products);
            vm.qtys = ko.observableArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

            var ps = vm.products();
            for (var i = 0; i < ps.length; i++) {
                ps[i].RegularCharge(ps[i].AlwaysRecurrent());
                ps[i].AlwaysOneOff(!settings.isMember || ps[i].AlwaysOneOff());
            }


            vm.totalTariff = ko.computed(function () {
                var ps = vm.products();
                var total = 0;
                for (var i = 0; i < ps.length; i++) {
                    if (ps[i].RegularCharge() && ps[i].Quantity())
                        total = total + (ps[i].Price() * parseInt(ps[i].Quantity()));
                }

                return total.toFixed(2);
            }, this);

            vm.totalOneOff = ko.computed(function () {
                var ps = vm.products();
                var total = 0;
                for (var i = 0; i < ps.length; i++) {
                    if (!ps[i].RegularCharge() && ps[i].Quantity())
                        total = total + (ps[i].Price() * parseInt(ps[i].Quantity()));
                }

                return total.toFixed(2);
            }, this);

            vm.selectedProducts = ko.computed(function () {
                return ko.utils.arrayFilter(vm.products(), function (item) {
                    return item.Quantity() > 0;
                });
            }, this);

            vm.showConfirmation = ko.observable(false);
            vm.confirmPurchase = function () {
                vm.doPurchase();
            };
            vm.doPurchase = function () {
                if (!vm.doPurchaseEnabled()) return;
                vm.doPurchaseEnabled(false);

                var plainJs = {
                    redirectUrl: settings.redirectUrl,
                    discountCode: vm.discountCode(),
                    products: ko.toJS(vm.selectedProducts)
                };
                $.when(sendData(plainJs))
                    .then(processPurchaseResult);

                function sendData(d) {
                    return $.ajax({
                        type: "POST",
                        url: '/' + currentLang + '/store/purchase',
                        contentType: "application/json",
                        dataType: "json",
                        data: JSON.stringify(d)
                    });
                }

                function processPurchaseResult(result) {
                    if (result.WasSuccessful) {
                        if (result.Redirect)
                            window.location = result.Redirect;
                        else
                            window.location = '/' + currentLang + '/allowances';
                    } else {
                        vm.doPurchaseEnabled(true);
                        showErrorMessage(result.Message);
                    }
                }
            };
            vm.closeConfirmation = function () {
                vm.showConfirmation(false);
            };
        }

        function applyBindings() {
            ko.applyBindings(vm);
        }
    }


    this.loadData = function () {
        loadData(vm);
    }


    var updatedPricesDb = _.debounce(updateProductPrices, 1000)
    vm.discountCode.subscribe(updatedPricesDb);
}


/**
 * Booking Manager
 */
function BookingManager(settings) {
    var bm = this;

    //Private fields
    var resources = settings.resources;
    var coworker = settings.coworker;
    var guestBookings = settings.guestBookings;
    var loginUrl = settings.loginUrl;
    var availableUrl = settings.availableUrl;
    var dayAvailableUrl = settings.dayAvailableUrl;
    var productsUrl = settings.productsUrl;
    var visitorsUrl = settings.visitorsUrl;
    var newBookingUrl = settings.newBookingUrl;
    var updateBookingUrl = settings.updateBookingUrl;
    var deleteBookingUrl = settings.deleteBookingUrl;
    var bookingUrl = settings.bookingUrl;
    var round = settings.roundMinutesTo;
    var dateTimeFormat = settings.dateTimeFormat;

    //Default date
    var start = roundDate(new Date(), round).toDate();
    var end = moment(start).add(1, 'hour').toDate();

    //View model definition
    var ViewModel = function () {
        this.datePickerOptions = {
            today: '',
            clear: '',
            selectYears: true,
            selectMonths: true,
            close: 'Chiudi',
            format: 'dddd, dd mmmm yyyy'
        };
        this.timePickerOptions = {
            today: '',
            clear: '',
            close: 'Chiudi',
        };
        this.currentBookingId = ko.observable(0);
        this.bookingFormInfo = ko.observable();
        this.bookingFormError = ko.observable();
        this.formError = ko.observable();
        this.selectedResource = ko.observable();
        this.resourceDetails = ko.observable({});
        this.selectedShift = ko.observable();
        this.resources = ko.observableArray(resources);
        this.startDate = ko.observable(start);
        this.endDate = ko.observable(end);
        this.startTime = ko.observable(start);
        this.endTime = ko.observable(end);
        this.price = ko.observable(0);
        this.bookingProducts = ko.observableArray();
        this.bookingVisitors = ko.observableArray();
        this.productQty = ko.observableArray(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]);
        this.noteFieldDisplayed = ko.observable(false);
        this.discountFieldDisplayed = ko.observable(false);
        this.notes = ko.observable();
        this.discountCode = ko.observable();
        this.disableSaveButton = ko.observable(false);
        this.discountInformation = ko.observable();
        this.disabledTimes = ko.observableArray([]);
        this.coworker = {
            FullName: ko.observable(),
            Email: ko.observable(),
            MobilePhone: ko.observable(),
            Address: ko.observable(),
            PostCode: ko.observable(),
            CityName: ko.observable(),
            State: ko.observable(),
            TaxIDNumber: ko.observable(),
            BillingEmail: ko.observable(),
        };

        /**
         * Triggers the validation of the registration form
         */
        this.validateRegistration = function () {
            vm.formError(null);
            if (vm.coworker.errors().length > 0) {
                vm.coworker.errors.showAllMessages();
                vm.formError('Please complete or correct the registration form');
                return;
            }

            vm.closeRegistationForm();
            bm.showNewBookingForm();
        }

        /**
         * Shows/Hides the notes field
         */
        this.toggleNoteField = function () {
            vm.noteFieldDisplayed(!vm.noteFieldDisplayed());
        }

        /**
         * Shows/hides the discount code field
         */
        this.toggleDiscountField = function () {
            vm.discountFieldDisplayed(!vm.discountFieldDisplayed());
        }

        /**
         * Triggers when any of the products linked with the selected resource is selected by the user
         */
        this.productChanged = function () {
            bm.getBookingPrice();
        }

        /**
         * Updates the currently selected resource when one is selected in the dropdown menu
         */
        this.selectResource = function (resourceId) {
            var resource = _.find(resources, { id: resourceId });
            vm.selectedResource(resource);
        }

        /**
         * Hides the registration form
         */
        this.closeRegistationForm = function () {
            vm.formError(null);
            $('#bookings-registration-form').modal('hide');
        }

        /**
         * Hides the booking form
         */
        this.closeBookingForm = function () {
            vm.bookingFormError(null);
            vm.resourceDetails({});
            $('#bookings-booking-form').modal('hide');
        }

        /**
         * Saves the currently loaded booking. Passes the call down to an internal function of this service
         */
        this.saveBooking = function () {
            bm.saveBooking();
        }

        /**
         * Deletes the currently loaded booking. Passes the call down to an internal function of this service
         */
        this.deleteBooking = function () {
            bm.deleteBooking();
        }

        /**
         * A computed property showing the duration of the booking
         */
        this.duration = function () {
            return moment.duration(moment(vm.endTime()).diff(vm.startTime()));
        }

        this.addVisitor = function () {
            vm.bookingVisitors.push({
                Id: null,
                Email: null,
                FullName: null
            });
        }

        this.removeVisitor = function (visitor) {
            vm.bookingVisitors.remove(visitor);
        }
        this.clearVisitors = function () {
            vm.bookingVisitors([]);
        }

        //search rooms
        this.searchResources = function () {
            var start = moment(vm.startTime()).format('YYYY-MM-DDT') + moment(vm.startTime()).format('HH:mm') + 'Z';
            var end = moment(vm.endTime()).format('YYYY-MM-DDT') + moment(vm.endTime()).format('HH:mm') + 'Z';
            window.location = settings.searchUrl + '?start=' + start + '&end=' + end;
        }



    }

    //Instantiate the view model
    var vm = new ViewModel();

    //Handlers

    var handlersInit = false;
    function initHandlers() {
        if (handlersInit) return;
        handlersInit = true;

        vm.discountCode.subscribe(function (newDiscountCode) {
            bm.getBookingPrice();
        })

        var lastStartTime = vm.startTime();
        vm.startTime.subscribe(function (newStartTime) {

            //round the date
            var start = roundDate(newStartTime, round).toDate();
            if (start.getTime() != newStartTime.getTime()) {
                vm.startTime(start);
                lastStartTime = vm.startTime();
                return;
            }

            var minutesDiff = moment(lastEndTime).diff(lastStartTime, 'minutes');
            vm.endTime(moment(newStartTime).add(minutesDiff, 'minutes').toDate());

            lastStartTime = vm.startTime();
            bm.getBookingPrice();

            if (settings.onDateChange)
                settings.onDateChange(vm);
        });

        vm.startDate.subscribe(function (newStartDate) {
            vm.startTime(moment(vm.startTime()).clone().date(moment(newStartDate).date()).toDate());
            vm.startTime(moment(vm.startTime()).clone().month(moment(newStartDate).month()).toDate());
            vm.startTime(moment(vm.startTime()).clone().year(moment(newStartDate).year()).toDate());
            //bm.dayAvailbility();
        });

        vm.endDate.subscribe(function (newEndDate) {
            if (newEndDate.getDate() != vm.endTime().getDate()) {
                vm.endTime(newEndDate);
                //bm.dayAvailbility();
            }
        });

        var lastEndTime = vm.endTime();
        vm.endTime.subscribe(function (newEndTime) {
            //round the date
            var end = roundDate(newEndTime, round).toDate();
            vm.endDate(end);

            if (end.getTime() != newEndTime.getTime()) {
                vm.endTime(end);
                lastEndTime = vm.endTime();
                return;
            }

            //Make sure the end time is never earlier than the start date
            if (newEndTime <= vm.startTime()) {
                vm.startTime(moment(newEndTime).add(-1, 'hour').toDate());
            }

            lastEndTime = vm.endTime();
            bm.getBookingPrice();

            if (settings.onDateChange)
                settings.onDateChange(vm);
        })

        vm.selectedShift.subscribe(function (newShift) {
            if (!newShift) return;
            var datePart = moment(vm.startDate()).format('YYYY-MM-DD');
            var shiftStart = moment(datePart + ' ' + newShift.Start);
            var shiftEnd = moment(datePart + ' ' + newShift.End);

            //Check if shift is across midnight
            if (shiftStart > shiftEnd)
                shiftEnd.set('day', shiftEnd.get('day') + 1);

            vm.startDate(shiftStart.toDate());
            vm.endDate(shiftEnd.toDate());
            vm.startTime(shiftStart.toDate());
            vm.endTime(shiftEnd.toDate());
        })

        vm.selectedResource.subscribe(function () {
            bm.getBookingPrice();
            bm.getBookingProducts();
            //bm.dayAvailbility();
        })

        if (settings.onDateChange)
            settings.onDateChange(vm);

    }

    //Methods
    this.applyBindings = function () {
        ko.validation.init({
            registerExtenders: true,
            messagesOnModified: true,
            insertMessages: false,
            parseInputAttributes: true,
            decorateInputElement: true
        }, true);
        vm.errors = ko.validation.group(vm, { deep: true });
        vm.coworker.errors = ko.validation.group(vm.coworker, { deep: true });
        ko.applyBindings(vm);

        initHandlers();
    }

    this.setDates = function (startDate, endDate) {
        vm.startDate(startDate);
        vm.endDate(endDate);
        vm.startTime(startDate);
        vm.endTime(endDate);
    }

    /**
     * Displays the booking form
     */
    this.showNewBookingForm = function () {

        bm.getBookingPrice();
        bm.getBookingProducts();
        $('#bookings-booking-form').modal({ backdrop: 'static' });
    }

    /**
     * Displays the registration form 
     */
    this.showRegistrationForm = function () {
        $('#bookings-registration-form').modal({ backdrop: 'static' });
    }

    /**
     * Starts the workflow to create a new booking asking the user to register or opening the new booking form.
     */
    this.createBooking = function (settings) {
        //Extract settings
        settings = settings || {};
        var resourceId = settings.resourceId;

        //Set the form in new booking mode 
        vm.currentBookingId(0);

        //Get the date with not time-zone. Full calendar sends this in the client timezone.
        var date = settings.date ? new Date(moment(settings.date).format('YYYY-MM-DDTHH:mmZ')) : new Date();
        var endDate = settings.endDate ? new Date(moment(settings.endDate).format('YYYY-MM-DDTHH:mmZ')) : moment(date).add(1, 'hour').toDate();

        vm.startDate(date);
        vm.endDate(endDate);
        vm.startTime(date);
        vm.endTime(endDate);

        //Preselect a room
        if (resourceId)
            vm.selectResource(parseInt(resourceId));

        //Login prompt
        if (coworker.Id == null && !guestBookings) {
            app.showQuestion(
                "Accedi per effettuare una prenotazione",
                "Devi avere con conto con WEB Business Hub per effettuare una prenotazione. Vuoi accedere ora?")
                .then(function (result) {
                    if (result)
                        window.location = loginUrl;
                })


            return;
        }

        //Register prompt
        if (coworker.Id == null && guestBookings) {
            app.showQuestion(
                "Sei già registrato o hai usato tutti i servizi di WEB Business Hub?",
                "Puoi effettuare le prenotazioni molto più velocemente se effettui il login con i dettagli del conto che ti abbiamo inviato quando ti sei registrato sul sito o hai effettuato l'ultima prenotazione.")
                .then(function (result) {
                    if (!result) {
                        bm.showRegistrationForm();
                    }
                    else {
                        window.location = loginUrl;
                    }
                });

            return;
        }


        //New booking form
        if (coworker != null) {
            bm.showNewBookingForm();
            return;
        }
    };

    /**
     * Retrieves the products associated with the selected booking
     */
    this.getBookingProducts = _.debounce(function () {
        if (vm.selectedResource() == null) return;

        var data = {
            bookingId: vm.currentBookingId(),
            resourceId: vm.selectedResource().id
        }
        $.post(productsUrl, data).then(function (result) {
            vm.bookingProducts(result);
        });
    }, 500);

    /**
     * Retrieves the visitor associated with the selected booking
     */
    this.getBookingVisitors = _.debounce(function () {
        if (vm.selectedResource() == null) return;
        var data = {
            bookingId: vm.currentBookingId()
        }
        $.post(visitorsUrl, data).then(function (result) {
            vm.bookingVisitors(result);
        });
    }, 500);

    this.dayAvailbility = function () {
        var resource = vm.selectedResource();
        if (!resource) return;
        console.log('resource', resource)
        var startDay = moment(vm.startDate()).startOf('day').format('YYYY-MM-DD');
        $.post(dayAvailableUrl + '?guid=' + resource.UniqueId + '&startTime=' + startDay + '&bookingId=' + vm.currentBookingId())
            .then(function (result) {
                var disableItems = _.map(result.AvailableSlots, function (item, index) {
                    var fromHour = parseInt(item.Time.split(':')[0]);
                    var fromMinutes = parseInt(item.Time.split(':')[1]);
                    var nextItem = index < result.AvailableSlots.length ? result.AvailableSlots[index + 1] : null;
                    if (!item.Available || item.Booked && (nextItem == null || !nextItem.Available || nextItem.Booked))
                        return { from: [fromHour, fromMinutes], to: [fromMinutes == 0 ? fromHour : fromHour + 1, fromMinutes == 0 ? 30 : 00] }
                    else
                        return {}
                })
                console.log('disableItems', disableItems)
                //vm.disabledTimes(disableItems)
            });
    }

    /**
     * Gets the price of this booking based on the selected resource, products and start and end times.
     */
    this.getBookingPrice = _.debounce(function () {
        vm.bookingFormInfo('Stiamo controllando se questa risorsa è disponibile. Si prega di attendere...')
        vm.bookingFormError(null);
        vm.price(null);


        if (vm.selectedResource() == null) {
            vm.discountInformation(null);
            vm.bookingFormError(null);
            vm.bookingFormInfo(null);
            return null;
        }

        var data = getBookingData();

        $.post(availableUrl, data)
            .then(function (result) {
                var newShifts = false;
                if ((vm.resourceDetails().Shifts == null || vm.resourceDetails().Shifts.length == 0) && result.Resource.Shifts.length > 0)
                    newShifts = true;

                if (vm.resourceDetails().Id != result.Resource.Id) {
                    vm.resourceDetails(result.Resource);
                    if (newShifts) return;
                }

                vm.price(result.Price);
                vm.discountInformation(result.discountCode);
                vm.bookingFormError(result.Message);
                vm.bookingFormInfo(null);
            });
    }, 1000);

    /**
     * Makes the HTTP request to update and existing booking or create a new booking
     */
    this.saveBooking = function () {
        //Set UI state while saving the booking
        vm.disableSaveButton(true);
        vm.bookingFormError(null);

        //Get the URL to create or save the booking.
        var url = vm.currentBookingId() > 0 ? updateBookingUrl : newBookingUrl;

        //Build request data and do request to create/update booking
        var data = getBookingData();
        $.ajax(url, {
            data: JSON.stringify(data),
            contentType: 'application/json',
            type: 'POST'
        }).
            then(function (result) {
                if (result.WasSuccessful) {
                    vm.disableSaveButton(false);
                    vm.closeBookingForm();
                    if (result.Value && result.Value.CancelUnpaidInvoices) {
                        app.showQuestion(
                            'Pagamento Richiesto',
                            'Una o più prenotazioni devono essere pagate prima di venire confermate')
                            .then(function (result) {
                                if (result)
                                    window.location = settings.paymentUrl;
                                else
                                    $('#calendar').fullCalendar('refetchEvents')
                            })
                    } else {
                        app.showMessage(result.Message).
                            then(function (result) {
                                $('#calendar').fullCalendar('refetchEvents')
                            });
                    }
                }
                else {
                    vm.disableSaveButton(false);
                    vm.bookingFormInfo(null);
                    vm.bookingFormError(result.Message);
                }
            }).
            fail(function () {
                vm.disableSaveButton(false);
                vm.bookingFormInfo(null);
                vm.bookingFormError('Ops! Non siamo riusciti a salvare questa prenotazione. Si prega di riprovare.');
            });
    }

    /**
     * Deletes the currently loaded booking.
     */
    this.deleteBooking = function () {
        vm.closeBookingForm();
        app.showQuestion('Vuoi annullare questa prenotazione?')
            .then(function (answer) {
                if (answer) {
                    var url = deleteBookingUrl + '/' + vm.currentBookingId();
                    $.post(url).
                        then(function (result) {
                            if (result.WasSuccessful) {
                                vm.closeBookingForm();
                                app.showMessage(result.Message).
                                    then(function () {
                                        vm.disableSaveButton(false);
                                        $('#calendar').fullCalendar('refetchEvents')
                                    });
                            }
                            else {
                                vm.bookingFormInfo(null);
                                vm.bookingFormError(result.Message);
                                bm.showNewBookingForm();
                                vm.disableSaveButton(false);
                            }
                        }).
                        fail(function () {
                            bm.showNewBookingForm();
                            vm.bookingFormError('Non siamo riusciti ad annullare questa prenotazione. Si prega di riprovare.');
                        });
                }
                else {
                    bm.showNewBookingForm();
                }
            });
    }

    /**
     * Loads the details of an existing booking.
     */
    this.showBooking = function (calEvent) {
        vm.currentBookingId(calEvent.id);
        $.get(bookingUrl + '/' + vm.currentBookingId()).then(function (response) {
            initHandlers();
            vm.disableSaveButton(false);
            vm.selectedResource(_.find(resources, { id: response.Value.ResourceId }));
            vm.resourceDetails(response.Resource);
            vm.startDate(moment(response.Value.FromTime.replace("Z", "")).toDate());
            vm.endDate(moment(response.Value.ToTime.replace("Z", "")).toDate());
            vm.startTime(moment(response.Value.FromTime.replace("Z", "")).toDate());
            vm.endTime(moment(response.Value.ToTime.replace("Z", "")).toDate());
            vm.notes(response.Value.Notes);
            selectShift(response.Value);
            bm.getBookingVisitors();
            bm.showNewBookingForm();
        });
    }

    //Utils functions

    var selectShift = function (booking) {
        var foundShift = _.find(vm.resourceDetails().Shifts, function (shift) {
            var shiftStart = moment('2000-01-01 ' + shift.Start);
            var shiftEnd = moment('2000-01-01 ' + shift.End);
            var selectedStart = moment.utc(booking.FromTime);
            var selectedEnd = moment.utc(booking.ToTime);

            if (shiftStart.get('hour') == selectedStart.get('hour') &&
                shiftStart.get('minute') == selectedStart.get('minute') &&
                shiftEnd.get('hour') == selectedEnd.get('hour') &&
                shiftEnd.get('minute') == selectedEnd.get('minute'))
                return true;

            return false;
        });

        if (foundShift != null && vm.selectedShift().Name != foundShift.Name)
            vm.selectedShift(foundShift);
    };

    /**
     * Builds an object with the data of the booking as selected in the booking form
     */
    function getBookingData() {
        return {
            Booking: {
                Notes: vm.notes(),
                DiscountCode: vm.discountCode(),
                Resource: {
                    Id: vm.selectedResource().id
                },
                Id: vm.currentBookingId(),
                FromTime: moment(vm.startDate()).format('YYYY-MM-DDT') + moment(vm.startTime()).format('HH:mm') + 'Z',
                ToTime: moment(vm.endDate()).format('YYYY-MM-DDT') + moment(vm.endTime()).format('HH:mm') + 'Z',
                BookingVisitors: _.map(vm.bookingVisitors(), function (x) {
                    return {
                        Id: x.Id,
                        Visitor: {
                            FullName: x.FullName,
                            Email: x.Email
                        }

                    }
                })
            },
            Coworker: ko.toJS(vm.coworker),
            Products: _.map(vm.bookingProducts(), function (x) {
                return {
                    ProductId: x.ProductId,
                    Quantity: x.Quantity,
                    Selected: x.Selected
                }
            }),
        };
    }

    /**
     * Rounds a date to the nearest "roundTo" minutes
     */
    function roundDate(date, roundTo) {
        var start = moment(date);
        start.millisecond(0);
        start.second(0);
        var remainder = (roundTo - start.minute()) % roundTo;
        return moment(start).add("minutes", remainder);
    }
}



/**
 * Directory Manager
 */

function DirectoryManager(baseUrl) {
    var self = this;
    var baseUrl = baseUrl;

    self.init = function () {

        $('.custom-filter-link, .tag-filter-link').click(function () {
            var cb = $(this);

            //Deselect all other filters in this category
            var index = cb.attr('data-cmindex');
            $('.custom-filter-link[data-cmindex=' + index + ']').not(cb).attr('data-selected', 'false');

            //Toggle clicked filter and any other link for the same filter
            cb.attr('data-selected', cb.attr('data-selected') == 'true' ? 'false' : 'true');
            $('.tag-filter-link[data-tag="' + cb.attr('data-tag') + '"]').not(cb).attr('data-selected', cb.attr('data-selected'));

            //Trigger search
            self.doSearch();
            return false;
        });
    }



    self.doSearch = function () {
        //tags
        var url = baseUrl;
        var tags = '';
        var customs = '';
        $('.tag-filter-link').each(function (k, el) {
            var cb = $(el);
            if (cb.attr('data-selected') == "true" && cb.attr('data-tag') != null)
                tags = tags.replace(cb.attr('data-tag').toLowerCase() + ',', '') + cb.attr('data-tag').toLowerCase() + ',';
        });


        tags = tags.substring(0, tags.length - 1);

        //custom fields
        $('.custom-filter-link').each(function (k, el) {
            var cb = $(el);
            if (cb.attr('data-selected') == "true" && cb.attr('data-value') != null)
                customs = customs + 'Custom' + cb.attr('data-cmindex') + '=' + cb.attr('data-value') + '&';
        });

        //Search
        var query = $('#query').val();
        var location = url + '?' + (tags ? 'tag=' + tags + '&' : '') + (customs ? customs : '') + (query ? '&query=' + query + '&' : '');
        location = location.substring(0, location.length - 1);
        window.location = location;

        return false;
    }

}

/** 
 * Provides basic validation and submition functions for HTML forms.
 */
function SimpleFormManager(settings) {
    var self = this;
    var viewModel = function () {
        var vm = this;
        vm.busy = ko.observable(false);
        vm.formData = ko.mapping.fromJS(settings.formData);

        vm.submitForm = function () {
            if (ko.validatedObservable) {
                var validatedvm = ko.validatedObservable(vm.formData);
                if (validatedvm.isValid() === false) {
                    vm.errors.showAllMessages(true);
                    app.showErrorMessage(settings.formError);
                    return false;
                }
            }

            vm.busy(true);
            return true;
        }
    }

    var vm = new viewModel();

    self.init = function () {

        ko.validation.init({
            registerExtenders: true,
            messagesOnModified: true,
            insertMessages: false,
            parseInputAttributes: true,
            decorateInputElement: true
        }, true);

        vm.errors = ko.validation.group(vm, { deep: true });
        ko.applyBindings(vm, document.getElementById(settings.formId));
    }

}

/**
 * Profile Manager
 */

function ProfileManager(settings) {
    var self = this;
    var navManager = new LateralNavigationManager();

    var ViewModel = function () {
        var vm = this;

        var hasBilllingDetails = settings.coworker.BillingAddress ||
            settings.coworker.BillingPostCode ||
            settings.coworker.BillingCityName ||
            settings.coworker.BillingState ||
            settings.coworker.BillingEmail;

        settings.coworker.UpdateBillingDetails = false;
        settings.coworker.Avatar = null;
        settings.coworker.Banner = null;
        settings.coworker.hasBillingDetails = hasBilllingDetails;
        settings.coworker.ProfileTagsArray = settings.coworker.ProfileTags != null ? (settings.coworker.ProfileTags + '').split(',') : [];
        settings.user.OldPassword = null;
        settings.user.NewPassword = null;
        settings.user.RepeatNewPassword = null;

        //Build custom field helpers
        _.forEach(settings.customFields, function (field) {
            if (field.AvailableOptions)
                field.AvailableOptionsArray = field.AvailableOptions.split(',');

            if (field.FieldType === 'Dropdown') {
                if (settings.coworker['Custom' + field.CustomFieldIndex])
                    settings.coworker['Custom' + field.CustomFieldIndex + 'Array'] = settings.coworker['Custom' + field.CustomFieldIndex].split(',');
                else
                    settings.coworker['Custom' + field.CustomFieldIndex + 'Array'] = [];
            }
        });


        this.coworker = ko.mapping.fromJS(settings.coworker);
        this.user = ko.mapping.fromJS(settings.user);
        this.profileTags = ko.mapping.fromJS(settings.profileTags);
        this.customFields = _.toArray(_.groupBy(settings.customFields, 'GroupName'));
        this.busy = ko.observable(false);

        this.datePickerOptions = {
            today: '',
            selectYears: 200,
            clear: 'Elimina',
            selectMonths: true,
            close: 'Chiudi',
            format: 'dddd, dd mmmm yyyy'
        };


        vm.coworker.DateOfBirth = ko.observable(moment(vm.coworker.DateOfBirth()).toDate());
        vm.coworker.BillingName.subscribe(function (newvalue) {
            if (!vm.coworker.CompanyName()) vm.coworker.CompanyName(newvalue);
        })

        /**
         * POSTs the profile form
         */
        this.saveDetails = function () {

            if (ko.validatedObservable) {
                var validatedvm = ko.validatedObservable(vm.coworker);
                if (validatedvm.isValid() === false) {
                    vm.errors.showAllMessages(true);
                    app.showErrorMessage(settings.formError);
                    return;
                }
            }

            vm.busy(true);

            //Convert select tag array into a comma separated string
            vm.coworker.ProfileTags(vm.coworker.ProfileTagsArray().join(','));

            //Process values for custom fields 
            for (var i = 1; i <= 30; i++) {
                //Convert arrays into comma separated strings.
                var prop = ko.unwrap(vm.coworker['Custom' + i + 'Array']);
                if (prop)
                    vm.coworker['Custom' + i] = prop.join(',');

                //Make sure boolean values are submitted as true
                if (ko.unwrap(vm.coworker['Custom' + i]) === 'True')
                    vm.coworker['Custom' + i]('true');
            }

            if (!vm.coworker.hasBillingDetails()) {
                vm.coworker.UpdateBillingDetails = true;
                vm.coworker.BillingAddress = null;
                vm.coworker.BillingPostCode = null;
                vm.coworker.BillingCityName = null;
                vm.coworker.BillingState = null;
                vm.coworker.BillingPostCode = null;
                vm.coworker.BillingEmail = null;
            };

            var data = {
                base64avatar: vm.coworker.Avatar(),
                base64banner: vm.coworker.Banner(),
                Coworker: vm.coworker,
                User: vm.user
            };

            $.ajax({
                url: settings.url,
                type: 'POST',
                data: ko.mapping.toJSON(data),
                cache: false,
                contentType: "application/json",
            })
                .done(function (result) {
                    if (result.SuccessMessage)
                        app.showMessage(settings.successMessage || result.SuccessMessage)
                            .done(function () {
                                if (data.base64avatar || data.base64banner || data.Coworker.DeleteAvatar() || data.Coworker.DeleteBanner())
                                    location.reload(true);
                            });
                    else
                        app.showErrorMessage(result.ErrorMessage || settings.errorMessage);

                    vm.busy(false);
                })
                .fail(function () {
                    app.showMessage(settings.errorMessage);
                    vm.busy(false);
                });

            return false;
        }
    }

    var vm = new ViewModel();

    self.init = function () {

        vm.user.RepeatNewPassword.extend({ areSame: vm.user.NewPassword });
        ko.validation.init({
            registerExtenders: true,
            messagesOnModified: true,
            insertMessages: false,
            parseInputAttributes: true,
            decorateInputElement: true
        }, true);


        vm.errors = ko.validation.group(vm, { deep: true });
        ko.applyBindings(vm);
        navManager.initLateralSmoothNav();

        vm.coworker.FullName.subscribe(function () {
            vm.coworker.Salutation(vm.coworker.FullName().split(' ')[0]);
        })
    }
}


/**
 * Team Profile Manager
 */

function TeamProfileManager(settings) {
    var self = this;

    var ViewModel = function () {
        var vm = this;

        settings.team.TeamLogo = null;
        settings.team.Banner = null;
        settings.team.ProfileTagsArray = (settings.team.ProfileTags + '').split(',')

        this.coworker = { hasBillingDetails: false };
        this.team = ko.mapping.fromJS(settings.team);
        this.profileTags = ko.mapping.fromJS(settings.profileTags);
        this.busy = ko.observable(false);

        /**
         * POSTs the profile form
         */
        this.saveDetails = function () {

            if (ko.validatedObservable) {
                var validatedvm = ko.validatedObservable(vm.coworker);
                if (validatedvm.isValid() === false) {
                    vm.errors.showAllMessages(true);
                    app.showErrorMessage(settings.formError);
                    return;
                }
            }

            vm.busy(true);

            //Convert select tag array into a comma separated string
            vm.team.ProfileTags(vm.team.ProfileTagsArray().join(','));

            var data = {
                base64teamLogo: vm.team.TeamLogo(),
                Team: vm.team,
            };

            $.ajax({
                url: settings.url,
                type: 'POST',
                data: ko.mapping.toJSON(data),
                cache: false,
                contentType: "application/json",
            })
                .done(function (result) {
                    if (result.SuccessMessage)
                        app.showMessage(settings.successMessage || result.SuccessMessage)
                            .done(function () {
                                if (data.base64teamLogo)
                                    location.reload(true);
                            });
                    else
                        app.showErrorMessage(result.ErrorMessage || settings.errorMessage);

                    vm.busy(false);
                })
                .fail(function () {
                    app.showMessage(settings.errorMessage);
                    vm.busy(false);
                });

            return false;
        }
    }

    var vm = new ViewModel();

    self.init = function () {

        ko.validation.init({
            registerExtenders: true,
            messagesOnModified: true,
            insertMessages: false,
            parseInputAttributes: true,
            decorateInputElement: true
        }, true);


        vm.errors = ko.validation.group(vm, { deep: true });
        ko.applyBindings(vm);


    }
}

function LateralNavigationManager() {
    this.initLateralSmoothNav = function () {
        /**
         *  Lateral navigation
         */
        if ($('#js-nav-smooth').length > 0) {
            $(window).scroll(function () {
                highlightCurrentPosition();
            })

            $('#js-nav-smooth li.active a').on('click', function (e) {
                var id = $(this).attr('href').split('#').pop();
                app.scrollToAnchorElement(id);
                e.preventDefault();
            });
        }

        highlightCurrentPosition();

        function highlightCurrentPosition() {
            var safeHeaderHeight = $('.site-header').height() + $('.navbar-sec').height() + 73;
            $('#js-nav-smooth li li').removeClass('active');
            var top = $(window).scrollTop();
            var anchors = $('#js-nav-smooth a');
            for (var i = anchors.length - 1; i >= 0; i--) {
                var v = anchors[i];
                var href = $(v).attr('href');
                if (!href) continue;

                var id = href.split('#').pop(),
                    $div = $('#' + id);

                if ($div.length == 0) continue;
                if ($div.is(':visible') == false) continue;

                if (($div.offset().top - safeHeaderHeight - 100) <= top) {
                    $(v).parent().addClass('active');
                    return;
                }
            }
        }
    }
}

/** 
 * Community Board Manager
 */
function CommunityBoardManager(settings) {
    var self = this;
    var ViewModel = function () {
        this.Subject = ko.observable(),
            this.Message = ko.observable(),
            this.Tags = ko.observable()
    }

    var vm = new ViewModel();

    this.applyStartFormBindings = function () {
        ko.validation.init({
            registerExtenders: true,
            messagesOnModified: true,
            insertMessages: false,
            parseInputAttributes: true,
            decorateInputElement: true
        }, true);
        vm.errors = ko.validation.group(vm, { deep: true });
        ko.applyBindings(vm);
    }

    self.init = function () {
        $(".timeago").timeago();

        $('.btn-like-thread').on('click', function () {
            var tid = $(this).attr('data-thread');
            var state = $(this).attr('data-state');
            if (state == 1)
                unlikeThread(this, tid);
            else
                likeThread(this, tid);
            return false;
        });


        $('.btn-like-message').on('click', function () {
            var mid = $(this).attr('data-message');
            var state = $(this).attr('data-state');
            if (state == 1)
                unlikeMessage(this, mid);
            else
                likeMessage(this, mid);

            return false;
        });



        $('.btn-follow-thread').on('click', function () {
            var tid = $(this).attr('data-thread');
            var state = $(this).attr('data-state');
            if (state == 1)
                unfollowThread(this, tid);
            else
                followThread(this, tid);

            return false;
        });



        $('.btn-mute-thread').on('click', function () {
            var tid = $(this).attr('data-thread');
            var state = $(this).attr('data-state');

            if (state == 1)
                unmuteThread(this, tid);
            else
                muteThread(this, tid);

            return false;
        });

        $("#reply-box").mentionsInput({
            onDataRequest: function (mode, query, callback) {
                $.get('/en/directory/json?query=' + query, function (responseData) {
                    responseData = $.map(responseData, function (e, i) {
                        return {
                            id: e.Id,
                            name: e.FullName,
                            type: 'contact',
                            avatar: e.AvatarUrl + '?w=20&h=20&noavatar=%2Fcontent%2Fthemes%2Fpublic%2Fdos%2Fimg%2Favatar-default.png'
                        }
                    });
                    callback.call(this, responseData);
                });
            }
        });

        $("#reply-box").keyup(function () {
            $("#reply-box").mentionsInput('val', function (text) {
                $('#reply-box-server').val(text);
            });
        });

        $("form").submit(function () {
            $("#reply-box").mentionsInput('val', function (text) {
                $('#reply-box-server').val(text);
            });
        });

        $('.thread-expand').click(function () {
            $(this).hide();
            var threadId = $(this).attr('data-thread');
            $('#thread-summary-' + threadId).hide();
            $('#thread-complete-' + threadId).show();
            return false;
        })

        if ($('#tags').length) {
            $('#tags').tagsinput({
                trimValue: true,
                freeInput: true,
                maxTags: 3,
                maxChars: 15
            });
        }

        $('.bootstrap-tagsinput').addClass('form-control')
    }

    self.deleteThread = function (deleteUrl) {
        app.showQuestion('Vuoi eliminare questo thread?')
            .then(function (result) {
                if (result === true)
                    window.location = deleteUrl;
            });
        return false;
    }

    self.deleteMessage = function (deleteUrl) {
        app.showQuestion('Vuoi eliminare questo messaggio?')
            .then(function (result) {
                if (result === true)
                    window.location = deleteUrl;
            });
        return false;
    }

    function likeThread(el, threadId) {
        $.post('/en/community/likeThread/' + threadId, function (data) {
            if (data.WasSuccessful) {
                if ($(el).find('span').length > 0)
                    $(el).find('span').text($(el).attr('data-off'));
                else
                    $(el).text($(el).attr('data-off'));
                $(el).attr('data-state', 1);
            }
            else $(el).text('Error!');
        });
    }

    function unlikeThread(el, threadId) {
        $.post('/en/community/unlikeThread/' + threadId, function (data) {
            if (data.WasSuccessful) {
                if ($(el).find('span').length > 0)
                    $(el).find('span').text($(el).attr('data-on'));
                else
                    $(el).text($(el).attr('data-on'));
                $(el).attr('data-state', 0);
            }
            else $(el).text('Error!');
        });
    }

    function likeMessage(el, messageId) {
        $.post('/en/community/likeMessage/' + messageId, function (data) {
            if (data.WasSuccessful) {
                if ($(el).find('span').length > 0)
                    $(el).find('span').text($(el).attr('data-off'));
                else
                    $(el).text($(el).attr('data-off'));
                $(el).attr('data-state', 1);
            }
            else $(el).text('Error!');
        });
    }

    function unlikeMessage(el, messageId) {
        $.post('/en/community/unlikeMessage/' + messageId, function (data) {
            if (data.WasSuccessful) {
                if ($(el).find('span').length > 0)
                    $(el).find('span').text($(el).attr('data-on'));
                else
                    $(el).text($(el).attr('data-on'));
                $(el).attr('data-state', 0);
            } else $(el).text('Error!');
        });
    }


    function followThread(el, threadId) {

        return $.post('/en/community/followThread/' + threadId, function (data) {
            if (data.WasSuccessful) {
                unmuteThread($('#mute-thread-' + threadId), threadId);
                $(el).text($(el).attr('data-off'));
                $(el).attr('data-state', 1);
                $('#follow-icon-thread-' + threadId).show();
            }
            else $(el).text('Error!');
        });
    }

    function unfollowThread(el, threadId) {
        return $.post('/en/community/unfollowThread/' + threadId, function (data) {
            if (data.WasSuccessful) {
                $(el).text($(el).attr('data-on'));
                $(el).attr('data-state', 0);
                $('#follow-icon-thread-' + threadId).hide();
            } else $(el).text('Error!');
        });
    }

    function muteThread(el, threadId) {

        return $.post('/en/community/muteThread/' + threadId, function (data) {
            if (data.WasSuccessful) {
                unfollowThread($('#follow-thread-' + threadId), threadId);
                $(el).text($(el).attr('data-off'));
                $(el).attr('data-state', 1);

                $(el).closest('.board-list__item').addClass('board-list__item--muted');

            }
            else $(el).text('Error!');
        });
    }

    function unmuteThread(el, threadId) {
        return $.post('/en/community/unmuteThread/' + threadId, function (data) {
            if (data.WasSuccessful) {
                $(el).text($(el).attr('data-on'));
                $(el).attr('data-state', 0);

                $(el).closest('.board-list__item').removeClass('board-list__item--muted');
            } else $(el).text('Error!');
        });
    }

}



/* KO Custom Bindings */
ko.bindingHandlers.pickadate = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {

        var valueUnwrapped = ko.unwrap(valueAccessor());
        var $input = $(element);

        //init date picker        
        $input.pickadate(valueUnwrapped.options);

        $input.pickadate('on', {
            open: function () {

            },
            set: function (thingSet) {
                if (thingSet.select)
                    ////Update the date  part of the selected date object by calculating minutes from midnight
                    var currentDate = moment(valueAccessor().value());
                if (!currentDate) return;
                // Your moment at midnight
                var mmtMidnight = currentDate.clone().startOf('day');
                // Difference in minutes
                var diffMinutes = currentDate.clone().diff(mmtMidnight, 'minutes');

                var newDate = moment(thingSet.select).startOf('day').add(diffMinutes, 'minutes');
                valueAccessor().value(newDate.toDate());
            }
        });
    },
    update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        var valueUnwrapped = ko.unwrap(valueAccessor());
        var date = valueUnwrapped.value()
        var $input = $(element);

        var mnt = moment(date)
        $input.pickadate('set', { select: mnt.toDate() });


    }
};

ko.bindingHandlers.pickatime = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        var valueUnwrapped = ko.unwrap(valueAccessor());
        var $input = $(element);

        //init time picke
        var options = valueUnwrapped.options;
        if (valueUnwrapped.disabledTimes) options.disable = valueUnwrapped.disabledTimes();
        $input.pickatime(options);

        //Update the time part of the selected date object
        $input.pickatime('on', {
            open: function () {
                var pickerElement = $input.next();
                var top = pickerElement.find(".picker__list-item--selected").index();
                var height = pickerElement.find(".picker__list-item--selected").outerHeight();

                pickerElement.find('.picker__holder').animate({
                    scrollTop: top * height - height
                }, 1);
            },
            set: function (thingSet) {
                if (!isNaN(thingSet.select) && thingSet.select >= 0) {
                    var value = valueAccessor().value();
                    if (value) {
                        var currentDate = moment(value).startOf('day');
                        currentDate = currentDate.add(thingSet.select, 'minutes').toDate();

                        //If the date has changed, update the observable
                        if (valueAccessor().value() && valueAccessor().value().getTime() != currentDate.getTime())
                            valueAccessor().value(currentDate);
                    }
                    else {
                        valueAccessor().value(currentDate);
                    }
                }
            }
        });

    },
    update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        var valueUnwrapped = ko.unwrap(valueAccessor());
        var date = valueUnwrapped.value()
        var options = valueUnwrapped.options;
        if (valueUnwrapped.disabledTimes) options.disable = valueUnwrapped.disabledTimes();

        var $input = $(element);
        ////Update the time part of the selected date object by calculating minutes from midnight
        var mmt = moment(date);
        // Your moment at midnight
        var mmtMidnight = mmt.clone().startOf('day');
        // Difference in minutes
        var diffMinutes = mmt.clone().diff(mmtMidnight, 'minutes');

        var selectedTime = $input.pickatime('get', 'select');
        if (selectedTime == null || diffMinutes != selectedTime.time) {
            $input.pickatime('set', { select: diffMinutes });


            var currentTime = $input.pickatime('get', 'select').time;
            var currentDate = mmtMidnight.isValid() ? mmtMidnight.startOf('day') : moment().startOf('day');
            valueAccessor().value(currentDate.add(currentTime, 'minutes').toDate());
        }

        $input.pickatime('set', { enable: true });
        if (options.disable) $input.pickatime('set', { disable: options.disable });

    }
};

ko.bindingHandlers.simplemde = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        var valueUnwrapped = ko.unwrap(valueAccessor());
        var value = valueUnwrapped.value()
        var $input = $(element);
        var simplemde = new SimpleMDE({
            status: false,
            element: $input[0]
        });

        if (value)
            simplemde.value(value);

        simplemde.codemirror.on("change", function () {
            valueAccessor().value(simplemde.value());
        });
    }
};

ko.bindingHandlers.tagsinput = {
    init: function (element, valueAccessor, allBindings) {
        var defaultOptions = {
        }
        var options = allBindings().tagsinputOptions || defaultOptions;
        var value = valueAccessor();
        var valueUnwrapped = ko.unwrap(value);

        var el = $(element);

        el.tagsinput(options);

        for (var i = 0; i < valueUnwrapped.length; i++) {
            el.tagsinput('add', valueUnwrapped[i]);
        }

        el.on('itemAdded', function (event) {
            if (valueUnwrapped.indexOf(event.item) === -1) {
                value.push(event.item);
            }
        })

        el.on('itemRemoved', function (event) {
            value.remove(event.item);
        });
    },
    update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        var value = valueAccessor();
        var valueUnwrapped = ko.unwrap(value);

        var el = $(element);
        var prev = el.tagsinput('items');

        var added = valueUnwrapped.filter(function (i) { return prev.indexOf(i) === -1; });
        var removed = prev.filter(function (i) { return valueUnwrapped.indexOf(i) === -1; });

        // Remove tags no longer in bound model
        for (var i = 0; i < removed.length; i++) {
            el.tagsinput('remove', removed[i]);
        }

        // Refresh remaining tags
        el.tagsinput('refresh');

        // Add new items in model as tags
        for (i = 0; i < added.length; i++) {
            el.tagsinput('add', added[i]);
        }
    }
};

ko.bindingHandlers.chosen = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        var $element = $(element);
        var options = ko.unwrap(valueAccessor());

        if (typeof options === 'object')
            $element.chosen(options);
        else
            $element.chosen();

        ['options', 'selectedOptions', 'value'].forEach(function (propName) {
            if (allBindings.has(propName)) {
                var prop = allBindings.get(propName);
                if (ko.isObservable(prop)) {
                    prop.subscribe(function () {
                        setTimeout(function () {
                            $element.trigger('chosen:updated');
                        }, 0);
                    });
                }

            }
        });
    },
    update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        var $element = $(element);
        setTimeout(function () {
            $element.trigger('chosen:updated');
        }, 0);

    }
}

ko.bindingHandlers.booleanValue = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        var observable = valueAccessor(),
            interceptor = ko.computed({
                read: function () {
                    return observable().toString();
                },
                write: function (newValue) {
                    observable(newValue === "true");
                }
            });

        ko.applyBindingsToNode(element, { value: interceptor });
    }
};

ko.partitioned = function (observableArray, count) {
    if (!observableArray) return [];
    var rows, partIdx, i, j, arr;

    arr = observableArray();
    if (!arr) return [];

    rows = [];
    for (i = 0, partIdx = 0; i < arr.length; i += count, partIdx += 1) {
        rows[partIdx] = [];
        for (j = 0; j < count; j += 1) {
            if (i + j >= arr.length) {
                break;
            }
            rows[partIdx].push(arr[i + j]);
        }
    }
    return rows;
};

ko.validation.rules['areSame'] = {
    getValue: function (o) {
        return (typeof o === 'function' ? o() : o);
    },
    validator: function (val, otherField) {
        return val === this.getValue(otherField);
    },
    message: 'I campi devono avere lo stesso valore'
};

//https://github.com/TooManyBees/knockoutjs-file-binding/blob/master/knockout-file-bind.js
ko.bindingHandlers['file'] = {
    init: function (element, valueAccessor, allBindings) {
        var fileContents, fileName, allowed, prohibited, reader;

        if ((typeof valueAccessor()) === "function") {
            fileContents = valueAccessor();
        } else {
            fileContents = valueAccessor()['data'];
            fileName = valueAccessor()['name'];

            allowed = valueAccessor()['allowed'];
            if ((typeof allowed) === 'string') {
                allowed = [allowed];
            }

            prohibited = valueAccessor()['prohibited'];
            if ((typeof prohibited) === 'string') {
                prohibited = [prohibited];
            }

            reader = (valueAccessor()['reader']);
        }

        reader || (reader = new FileReader());
        reader.onloadend = function () {
            fileContents(reader.result);
        }

        var handler = function () {
            var file = element.files[0];

            // Opening the file picker then canceling will trigger a 'change'
            // event without actually picking a file.
            if (file === undefined) {
                fileContents(null)
                return;
            }

            if (allowed) {
                if (!allowed.some(function (type) { return type === file.type })) {
                    console.log("File " + file.name + " is not an allowed type, ignoring.")
                    fileContents(null)
                    return;
                }
            }

            if (prohibited) {
                if (prohibited.some(function (type) { return type === file.type })) {
                    console.log("File " + file.name + " is a prohibited type, ignoring.")
                    fileContents(null)
                    return;
                }
            }

            reader.readAsDataURL(file); // A callback (above) will set fileContents
            if (typeof fileName === "function") {
                fileName(file.name)
            }
        }

        ko.utils.registerEventHandler(element, 'change', handler);
    }
};



