window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.webcore = window.eviivo.webcore ? window.eviivo.webcore : {};
window.eviivo.webcore.formatting = window.eviivo.webcore.formatting ? window.eviivo.webcore.formatting : {};

window.eviivo.webcore.formatting.datetime = function () {
    var namespace = "eviivo.webcore.formatting.datetime";

    function evDisplayLongTime(date, culture) {
        // hh:MM:ss
        return formatTime(date, culture, "long");
    }
    function evDisplayShortTime(date, culture) {
        // hh:MM
        return formatTime(date, culture, "short");
    }
    function evDisplayLongDate(date, culture) {
        // ddd dd-mmm-yy
        return formatDate(date, culture, "long");
    }
    function evDisplayMediumDate(date, culture) {
        // dd-mmm-yy
        return formatDate(date, culture, "medium");
    }
    function evDisplayShortDate(date, culture) {
        // dd-mmm-yy (should be dd-mm-yy)
        return formatDate(date, culture, "short");
    }
    function evDisplayLongDateTime(date, culture) {
        return formatDate(date, culture, "long") + " " + formatTime(date, culture, "short");
    }
    function evDisplayExtraLongDateTime(date, culture) {
        return formatDate(date, culture, "long") + " " + formatTime(date, culture, "long");
    }
    function evDisplayLongTimeDate(date, culture) {
        return formatTime(date, culture, "short") + " " + formatDate(date, culture, "long");
    }
    function evDisplayMediumDateTime(date, culture) {
        return formatDate(date, culture, "medium") + " " + formatTime(date, culture, "short");
    }
    function evDisplayMediumTimeDate(date, culture) {
        return formatTime(date, culture, "short") + " " + formatDate(date, culture, "medium");
    }
    function evDisplayShortDateTime(date, culture) {
        return formatDate(date, culture, "short") + " " + formatTime(date, culture, "short");
    }
    function evDisplayShortTimeDate(date, culture) {
        return formatTime(date, culture, "short") + " " + formatDate(date, culture, "short");
    }

    function evStandardIso8601Date(date) {
        // yyyymmdd
        return formatDate(date, "iso8601", "short");
    }
    function evStandardIso8601DateTimeUtc(date) {
        // yyyymmddThhmmss
        return formatDate(date, "iso8601", "short") + "T" + formatTime(date, "iso8601", "short");
    }
    function evStandardIso8601DateExtended(date) {
        // yyyy-mm-dd
        return formatDate(date, "iso8601", "long");
    }

    function evStandardIso8601DateTimeUtcExtended(date) {
        // yyyy-mm-ddThh:mm:ss
        return formatDate(date, "iso8601", "long") + "T" + formatTime(date, "iso8601", "long");
    }

    function evStandard24HourTime(date) {
        return formatTime(date, "iso8601", "short");
    }

    // public method to - assess current culture passed into function and calculate calendar first day offset
    function evStandardFirstDayCalendarOffset(culture) {
        return getFirstDayCalendarOffset(culture.toLowerCase());
    }

    // public method to - assess current culture passed into function and calculate calendar first day offset
    function evGetDateDisplayFormat(culture, type) {
        return getDateDisplayFormat(culture.toLowerCase(), type);
    }

    // private method to - assess current culture passed into function and calculate calendar first day offset
    function getFirstDayCalendarOffset(culture) {
        var regionals = window.eviivo.webcore.formatting.regionals[culture];
        if (regionals == null) {
            regionals = window.eviivo.webcore.formatting.regionals["default"];
        }
        return regionals.firstDayCalendarOffset;
    }

    // private method to - assess current culture passed into function and calculate calendar first day offset
    function getDateDisplayFormat(culture, type) {
        var regionals = window.eviivo.webcore.formatting.regionals[culture];
        if (regionals == null) {
            regionals = window.eviivo.webcore.formatting.regionals["default"];
        }
        if (type === "long") {
            return regionals.jQDateFormat.long;
        } else if (type === "short") {
            return regionals.jQDateFormat.short;
        } else {
            return regionals.jQDateFormat.medium;
        }
    }

    //NOTE: replace and indexOf are case sensitive
    function formatDate(date, culture, formatType) {
        if (date == null) {
            return null;
        }

        if (culture == null || culture == "") {
            throw (namespace + ": please specify the 'culture' code parameter");
        }

        culture = culture.toLowerCase();

        if (date instanceof Date) {
            var regionals = window.eviivo.webcore.formatting.regionals[culture];
            if (regionals == null) {
                regionals = window.eviivo.webcore.formatting.regionals["default"];
            }

            var dateResult = regionals.dateFormat[formatType].slice(0);
            var day = date.getDate();
            var weekDay = date.getDay();

            var regExNonPaddedDay1 = new RegExp("([\\w\\d ]+[ \\.\\-/,])(d{1})([ \\.\\-/,]+)([\\w\\d \\.\\-/,]+)");
            var regExNonPaddedDay2 = new RegExp("[^yYdDmM \\.\\-/,](d{1})([ \\.\\-/,]+)([yYdDmM \\.\\-/,]+)");
            if (regExNonPaddedDay1.test(dateResult)) {
                dateResult = dateResult.replace(regExNonPaddedDay1, "$1" + day + "$3$4");
            } else if (regExNonPaddedDay2.test(dateResult)) {
                dateResult = dateResult.replace(regExNonPaddedDay2, day + "$2$3");
            }

            if (regionals.dayNames != null) {
                dateResult = dateResult.replace("dddd", regionals.dayNames.long[weekDay]);
                dateResult = dateResult.replace("ddd", regionals.dayNames.medium[weekDay]);
                dateResult = dateResult.replace("DD", regionals.dayNames.short[weekDay]);
            }
            dateResult = dateResult.replace("dd", padZero(day));

            var month = date.getMonth();
            var regExNonPaddedMonth1 = new RegExp("([\\w\\d ]+[ \\.\\-/,])(M{1})([ \\.\\-/,]+)([\\w\\d \\.\\-/,]+)");
            var regExNonPaddedMonth2 = new RegExp("[^yYdDmM \\.\\-/,](M{1})([ \\.\\-/,]+)([yYdDmM \\.\\-/,]+)");
            if (regExNonPaddedMonth1.test(dateResult)) {
                dateResult = dateResult.replace(regExNonPaddedMonth1, "$1" + month + "$3$4");
            } else if (regExNonPaddedMonth2.test(dateResult)) {
                dateResult = dateResult.replace(regExNonPaddedMonth2, month + "$2$3");
            }

            if (regionals.monthNames != null) {
                dateResult = dateResult.replace("MMMM", regionals.monthNames.long[month]);
                dateResult = dateResult.replace("MMM", regionals.monthNames.medium[month]);
            }
            dateResult = dateResult.replace("MM", padZero(month + 1));

            dateResult = dateResult.replace("yyyy", date.getFullYear());
            dateResult = dateResult.replace("yy", date.getFullYear().toString().substring(2));
            return dateResult;
        } else {
            return date.toLocaleString();
        }
    }

    //NOTE: replace and indexOf are case sensitive
    function formatTime(date, culture, formatType) {
        if (date == null) {
            return null;
        }

        if (culture == null || culture == "") {
            throw (namespace + ": please specify the 'culture' code parameter");
        }

        culture = culture.toLowerCase();

        if (date instanceof Date) {
            var regionals = window.eviivo.webcore.formatting.regionals[culture];
            if (regionals == null) {
                regionals = window.eviivo.webcore.formatting.regionals["default"];
            }

            var timeResult = regionals.clockFormat[formatType].slice(0);

            var cFormat = regionals.clockFormat.mode;
            if (cFormat == null) {
                cFormat = 24;
            }

            if (timeResult.indexOf("tt") < 0 && timeResult.indexOf("t") > -1) {
                timeResult = timeResult.replace("t", cFormat == 24 ? "hh:mm" : "h:mm tt");
            }

            if (timeResult.indexOf("tt") < 0 && timeResult.indexOf("T") > -1) {
                timeResult = timeResult.replace("T", cFormat == 24 ? "hh:mm:ss" : "h:mm:ss tt");
            }

            var hours = date.getHours();
            if (cFormat == 12) {
                var ampm = hours >= 12 ? "PM" : "AM";
                timeResult = timeResult.replace("tt", ampm);
                hours = hours % 12;
                hours = hours ? hours : 12; // the hour '0' should be '12'
            }
            timeResult = timeResult.replace("hh:", padZero(hours) + ":");
            timeResult = timeResult.replace("h:", hours + ":");
            timeResult = timeResult.replace(":mm", ":" + padZero(date.getMinutes()));
            timeResult = timeResult.replace(":ss", ":" + padZero(date.getSeconds()));

            return timeResult;
        } else {
            return date.toLocaleString();
        }
    }

    function padZero(n) { return n < 10 ? '0' + n : n; }

    return {
        evDisplayLongTime: evDisplayLongTime,
        evDisplayShortTime: evDisplayShortTime,
        evDisplayLongDate: evDisplayLongDate,
        evDisplayMediumDate: evDisplayMediumDate,
        evDisplayShortDate: evDisplayShortDate,
        evDisplayLongDateTime: evDisplayLongDateTime,
        evDisplayExtraLongDateTime: evDisplayExtraLongDateTime,
        evDisplayMediumDateTime: evDisplayMediumDateTime,
        evDisplayShortDateTime: evDisplayShortDateTime,
        evDisplayLongTimeDate: evDisplayLongTimeDate,
        evDisplayMediumTimeDate: evDisplayMediumTimeDate,
        evDisplayShortTimeDate: evDisplayShortTimeDate,
        evStandard24HourTime: evStandard24HourTime,
        evStandardIso8601Date: evStandardIso8601Date,
        evStandardIso8601DateTimeUtc: evStandardIso8601DateTimeUtc,
        evStandardIso8601DateExtended: evStandardIso8601DateExtended,
        evStandardIso8601DateTimeUtcExtended: evStandardIso8601DateTimeUtcExtended,
        evStandardFirstDayCalendarOffset: evStandardFirstDayCalendarOffset,
        evGetDateDisplayFormat: evGetDateDisplayFormat
    };
}();

Date.prototype.evGetDateDisplayFormat = function (culture, type) {
    return eviivo.webcore.formatting.datetime.evGetDateDisplayFormat(culture, type);
}

Date.prototype.evStandardFirstDayCalendarOffset = function (culture) {
    return eviivo.webcore.formatting.datetime.evStandardFirstDayCalendarOffset(culture);
}

Date.prototype.evDisplayLongTime = function (culture) {
    return eviivo.webcore.formatting.datetime.evDisplayLongTime(this, culture);
}

Date.prototype.evDisplayShortTime = function (culture) {
    return eviivo.webcore.formatting.datetime.evDisplayShortTime(this, culture);
}

Date.prototype.evDisplayLongDate = function (culture) {
    return eviivo.webcore.formatting.datetime.evDisplayLongDate(this, culture);
}

Date.prototype.evDisplayMediumDate = function (culture) {
    return eviivo.webcore.formatting.datetime.evDisplayMediumDate(this, culture);
}

Date.prototype.evDisplayShortDate = function (culture) {
    return eviivo.webcore.formatting.datetime.evDisplayShortDate(this, culture);
}

Date.prototype.evDisplayLongDateTime = function (culture) {
    return eviivo.webcore.formatting.datetime.evDisplayLongDateTime(this, culture);
}

Date.prototype.evDisplayExtraLongDateTime = function (culture) {
    return eviivo.webcore.formatting.datetime.evDisplayExtraLongDateTime(this, culture);
}

Date.prototype.evDisplayMediumDateTime = function (culture) {
    return eviivo.webcore.formatting.datetime.evDisplayMediumDateTime(this, culture);
}

Date.prototype.evDisplayShortDateTime = function (culture) {
    return eviivo.webcore.formatting.datetime.evDisplayShortDateTime(this, culture);
}

Date.prototype.evDisplayLongTimeDate = function (culture) {
    return eviivo.webcore.formatting.datetime.evDisplayLongTimeDate(this, culture);
}

Date.prototype.evDisplayMediumTimeDate = function (culture) {
    return eviivo.webcore.formatting.datetime.evDisplayMediumTimeDate(this, culture);
}

Date.prototype.evDisplayShortTimeDate = function (culture) {
    return eviivo.webcore.formatting.datetime.evDisplayShortTimeDate(this, culture);
}

Date.prototype.evStandardIso8601Date = function () {
    return eviivo.webcore.formatting.datetime.evStandardIso8601Date(this);
}
Date.prototype.evStandardIso8601DateTimeUtc = function () {
    return eviivo.webcore.formatting.datetime.evStandardIso8601DateTimeUtc(this);
}
Date.prototype.evStandardIso8601DateExtended = function () {
    return eviivo.webcore.formatting.datetime.evStandardIso8601DateExtended(this);
}
Date.prototype.evStandardIso8601DateTimeUtcExtended = function () {
    return eviivo.webcore.formatting.datetime.evStandardIso8601DateTimeUtcExtended(this);
}

Date.prototype.evStandard24HourTime = function () {
    return eviivo.webcore.formatting.datetime.evStandard24HourTime(this);
}

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.webcore.formatting = window.eviivo.webcore.formatting ? window.eviivo.webcore.formatting : {};
window.eviivo.webcore.formatting.regionals = window.eviivo.webcore.formatting.regionals ? window.eviivo.webcore.formatting.regionals : {};

window.eviivo.webcore.formatting.regionals["iso8601"] = {
    monthNames: null,
    dayNames: null,
    dateFormat: {
        long: "yyyy-MM-dd",
        medium: null,
        short: "yyyyMMdd"
    },
    jQDateFormat: {
        long: "yy-mm-dd",
        medium: null,
        short: "yymmdd"
    },
    clockFormat: {
        mode: 24,
        long: "hh:mm:ss",
        short: "hhmmss"
    },
    firstDayCalendarOffset: 1
};

window.eviivo.webcore.formatting.regionals["default"] = {
    monthNames: {
        long: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
        medium: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
        short: null
    },
    dayNames: {
        long: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
        medium: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
        short: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
    },
    dateFormat: {
        long: "ddd dd MMM yyyy",
        medium: "dd MMM yyyy",
        short: "dd-MM-yy"
    },
    jQDateFormat: {
        long: "D dd M yy",
        medium: "d M yy",
        short: "dd-M-y"
    },
    clockFormat: {
        mode: 24,
        long: "T",
        short: "t"
    },
    firstDayCalendarOffset: 1
};

window.eviivo.webcore.formatting.regionals["en-gb"] = {
    monthNames: {
        long: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
        medium: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
        short: null
    },
    dayNames: {
        long: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
        medium: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
        short: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
    },
    dateFormat: {
        long: "ddd dd MMM yyyy",
        medium: "dd MMM yyyy",
        short: "dd-MM-yy"
    },
    jQDateFormat: {
        long: "D dd M yy",
        medium: "d M yy",
        short: "dd-M-y"
    },
    clockFormat: {
        mode: 24,
        long: "T",
        short: "t"
    },
    firstDayCalendarOffset: 1
};

window.eviivo.webcore.formatting.regionals["en-us"] = {
    monthNames: {
        long: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
        medium: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
        short: null
    },
    dayNames: {
        long: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
        medium: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
        short: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
    },
    dateFormat: {
        long: "ddd MMM d, yyyy",
        medium: "MMM d, yyyy",
        short: "M/d/yy"
    },
    jQDateFormat: {
        long: "D M d, yy",
        medium: "M d, yy",
        short: "m/d/y"
    },
    clockFormat: {
        mode: 12,
        long: "T",
        short: "t"
    },
    firstDayCalendarOffset: 0
};

window.eviivo.webcore.formatting.regionals["fr-fr"] = {
    monthNames: {
        long: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'],
        medium: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'],
        short: null
    },
    dayNames: {
        long: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
        medium: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
        short: ['di', 'lu', 'ma', 'me', 'je', 've', 'sa']
    },
    dateFormat: {
        long: "ddd dd MMM yyyy",
        medium: "dd MMM yyyy",
        short: "dd-MM-yy"
    },
    jQDateFormat: {
        long: "D dd M yy",
        medium: "d M yy",
        short: "dd-M-y"
    },
    clockFormat: {
        mode: 24,
        long: "T",
        short: "t"
    },
    firstDayCalendarOffset: 1
};

window.eviivo.webcore.formatting.regionals["it-it"] = {
    monthNames: {
        long: ['gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', 'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre'],
        medium: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'],
        short: null
    },
    dayNames: {
        long: ['domenica', 'lunedì', 'martedì', 'mercoledì', 'giovedì', 'venerdì', 'sabato'],
        medium: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'],
        short: ['do', 'lu', 'ma', 'me', 'gi', 've', 'sa']
    },
    dateFormat: {
        long: "ddd dd MMM yyyy",
        medium: "dd MMM yyyy",
        short: "dd-MM-yy"
    },
    jQDateFormat: {
        long: "D dd M yy",
        medium: "d M yy",
        short: "dd-M-y"
    },
    clockFormat: {
        mode: 24,
        long: "T",
        short: "t"
    },
    firstDayCalendarOffset: 1
};

window.eviivo.webcore.formatting.regionals["de-de"] = {
    monthNames: {
        long: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
        medium: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
        short: null
    },
    dayNames: {
        long: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
        medium: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
        short: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']
    },
    dateFormat: {
        long: "ddd dd MMM yyyy",
        medium: "dd MMM yyyy",
        short: "dd-MM-yy"
    },
    jQDateFormat: {
        long: "D dd M yy",
        medium: "d M yy",
        short: "dd-M-y"
    },
    clockFormat: {
        mode: 24,
        long: "T",
        short: "t"
    },
    firstDayCalendarOffset: 1
};

window.eviivo.webcore.formatting.regionals["pt-pt"] = {
    monthNames: {
        long: ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'],
        medium: ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'],
        short: null
    },
    dayNames: {
        long: ['domingo', 'segunda-feira', 'terça-feira', 'quarta-feira', 'quinta-feira', 'sexta-feira', 'sábado'],
        medium: ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb'],
        short: ['D', 'S', 'T', 'Q', 'Q', 'S', 'S']
    },
    dateFormat: {
        long: "ddd dd MMM yyyy",
        medium: "dd MMM yyyy",
        short: "dd-MM-yy"
    },
    jQDateFormat: {
        long: "D dd M yy",
        medium: "d M yy",
        short: "dd-M-y"
    },
    clockFormat: {
        mode: 24,
        long: "T",
        short: "t"
    },
    firstDayCalendarOffset: 1
};

window.eviivo.webcore.formatting.regionals["pt-br"] = {
    monthNames: {
        long: ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'],
        medium: ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'],
        short: null
    },
    dayNames: {
        long: ['domingo', 'segunda-feira', 'terça-feira', 'quarta-feira', 'quinta-feira', 'sexta-feira', 'sábado'],
        medium: ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb'],
        short: ['D', 'S', 'T', 'Q', 'Q', 'S', 'S']
    },
    dateFormat: {
        long: "ddd dd MMM yyyy",
        medium: "dd MMM yyyy",
        short: "dd-MM-yy"
    },
    jQDateFormat: {
        long: "D dd M yy",
        medium: "d M yy",
        short: "dd-M-y"
    },
    clockFormat: {
        mode: 24,
        long: "T",
        short: "t"
    },
    firstDayCalendarOffset: 1
};

window.eviivo.webcore.formatting.regionals["es-es"] = {
    monthNames: {
        long: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'],
        medium: ['ene.', 'feb.', 'mar.', 'abr.', 'may.', 'jun.', 'jul.', 'ago.', 'sep.', 'oct.', 'nov.', 'dic.'],
        short: null
    },
    dayNames: {
        long: ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'],
        medium: ['do.', 'lu.', 'ma.', 'mi.', 'ju.', 'vi.', 'sá.'],
        short: ['D', 'S', 'T', 'Q', 'Q', 'S', 'S']
    },
    dateFormat: {
        long: "ddd dd MMM yyyy",
        medium: "dd MMM yyyy",
        short: "dd-MM-yy"
    },
    jQDateFormat: {
        long: "D dd M yy",
        medium: "d M yy",
        short: "dd-M-y"
    },
    clockFormat: {
        mode: 24,
        long: "T",
        short: "t"
    },
    firstDayCalendarOffset: 1
};
"use strict";

window.eviivo = window.eviivo || {};

window.eviivo.customDropdown = (function($, ev) {
    
    var predefinedEffects = Object.freeze({
        fadeIn: function (complete) { $(this).fadeIn({ complete: complete }); },
        fadeOut: function (complete) { $(this).fadeOut({ complete: complete }); },
        slideUp: function (complete) { $(this).slideUp({ complete: complete }); },
        slideDown: function (complete) { $(this).slideDown({ complete: complete }); }
    });

    var $document = $(document);

    return function($activationElement, $dropdownElement, options) {
        $activationElement = parseElement($activationElement, "$activationElement");
        $dropdownElement = parseElement($dropdownElement, "$dropdownElement");

        var $loadingOverlay = $('<div class="loading-overlay"></div>').appendTo($dropdownElement);

        options = parseOptions($activationElement, $dropdownElement, options);

        var attachedEvents = (function() {
            var events = [];

            function assertNotDestroyed() {
                if (attachedEvents === null) {
                    throw "This dropdown has been destroyed";
                }
            }

            function destroy() {
                var toDestroy = events;
                events = null;

                toDestroy.forEach(function(event) {
                    event.$el.off(event.name, event.callback);
                });
            }

            function attachEvent($element, eventName, callback) {
                assertNotDestroyed();

                if (!($element instanceof $)) {
                    throw "$element must be a jQuery object";
                }

                if (eventName === undefined || eventName === null || eventName === "" || /^\W+$/.test(eventName)) {
                    throw "eventName must be a valid event";
                }

                if (typeof (callback) !== "function") {
                    throw "callback must be a function";
                }

                events.push({ $el: $element, name: eventName, callback: callback });
                $element.on(eventName, callback);
            }

            return Object.freeze({
                assertNotDestroyed: assertNotDestroyed,
                destroy: destroy,
                attachEvent: attachEvent
            });
        })();

        attachedEvents.attachEvent($activationElement, 'click', toggle);
        attachedEvents.attachEvent($document, 'keydown', ev.onKey($.ui.keyCode.ESCAPE, hide));
        attachedEvents.attachEvent($document, 'click', ev.outsideOf($activationElement, $dropdownElement, hide));

        return Object.freeze({
            show: show,
            hide: hide,
            toggle: toggle,

            loading: Object.freeze({
                show: showLoading,
                hide: hideLoading,
                toggle: toggleLoading
            }),

            get isVisible() { return isVisible(); },
            destroy: attachedEvents.destroy,
            get $dropdownElement() {
                assertNotDestroyed();
                return $($dropdownElement.get(0));
            },
            get $activationElement() {
                assertNotDestroyed();
                return $($activationElement.get(0));
            }
        });

        function show() {
            if (!isVisible()) {
                $dropdownElement.trigger("customDropdown.beforeShow");
                options.show(function() { $(this).trigger("customDropdown.show"); });
            }
        }

        function hide() {
            if (isVisible()) {
                $dropdownElement.trigger("customDropdown.beforeHide");
                options.hide(function () {
                    $loadingOverlay.removeClass('show');
                    $(this).trigger("customDropdown.hide");
                });
            }
        }

        function toggle() {
            if (isVisible()) {
                hide();
            } else {
                show();
            }
        }

        function showLoading() {
            if (isVisible()) {
                $loadingOverlay.addClass('show');
            }
        }

        function hideLoading() {
            if (isVisible()) {
                $loadingOverlay.removeClass('show');
            }
        }

        function toggleLoading() {
            if (isVisible()) {
                $loadingOverlay.toggleClass('show');
            }
        }

        function isVisible() {
            attachedEvents.assertNotDestroyed();
            return $dropdownElement.is(":visible");
        }
    };
    
    function parseElement($el, name) {
        if ($el === undefined || $el === null) {
            throw "You must pass an " + name;
        }

        if ($el instanceof HTMLElement) {
            return $($el);
        } else if ($el instanceof window.jQuery) {
            if ($el.length === 0) {
                throw name + " is a jQuery object, but contains no element";
            } else if ($el.length > 1) {
                throw name + " contains more than one element - you must only provide one";
            } else {
                return $($el.get(0));
            }
        } else {
            throw name + " is neither an HTML Element, nor a jQuery object";
        }
    }

    function parseOptions($activationElement, $dropdownElement, options) {
        options = options || {};

        var parsedOptions = {};

        if (options.show) {
            var type = typeof (options.show);
            if (type === "string") {
                if (predefinedEffects.hasOwnProperty(options.show)) {
                    parsedOptions.show = predefinedEffects[options.show].bind($dropdownElement.get(0));
                } else if (options.show === "fast") {
                    parsedOptions.show = function(complete) { $(this).show('fast', complete); }.bind($dropdownElement.get(0));
                } else if (options.show === "slow") {
                    parsedOptions.show = function(complete) { $(this).show('slow', complete); }.bind($dropdownElement.get(0));
                } else {
                    throw "options.show, when specified as a string, must be one of the recognised predefined effects";
                }
            } else if (type === "number") {
                var duration = options.show;
                parsedOptions.show = function(complete) { $(this).show(duration, complete ); }.bind($dropdownElement.get(0));
            } else if (type === "function") {
                if (options.show.length === 1) {
                    parsedOptions.show = options.show.bind($dropdownElement.get(0));
                } else {
                    throw "options.show, when specified as a function, must accept a single parameter, the completion function";
                }
            } else {
                throw "options.show does not have a recognised value";
            }
        } else {
            parsedOptions.show = function(complete) { $(this).show('fast', complete); }.bind($dropdownElement.get(0));
        }

        if (options.hide) {
            var type = typeof (options.hide);
            if (type === "string") {
                if (predefinedEffects.hasOwnProperty(options.hide)) {
                    parsedOptions.hide = predefinedEffects[options.hide].bind($dropdownElement.get(0));
                } else if (options.hide === "fast") {
                    parsedOptions.hide = function (complete) { $(this).hide('fast', complete); }.bind($dropdownElement.get(0));
                } else if (options.hide === "slow") {
                    parsedOptions.hide = function (complete) { $(this).hide('slow', complete); }.bind($dropdownElement.get(0));
                } else {
                    throw "options.hide, when specified as a string, must be one of the recognised predefined effects";
                }
            } else if (type === "number") {
                var duration = options.hide;
                parsedOptions.hide = function (complete) { $(this).hide(duration, complete); }.bind($dropdownElement.get(0));
            } else if (type === "function") {
                if (options.hide.length === 1) {
                    parsedOptions.hide = options.hide.bind($dropdownElement.get(0));
                } else {
                    throw "options.hide, when specified as a function, must accept a single parameter, the completion function";
                }
            } else {
                throw "options.hide does not have a recognised value";
            }
        } else {
            parsedOptions.hide = function (complete) { $(this).hide(0, complete); }.bind($dropdownElement.get(0));
        }

        return Object.freeze(parsedOptions);
    }
})(window.jQuery, window.eviivo.utils.eventHelpers);
"use strict";

window.eviivo = window.eviivo || {};
window.eviivo.jQuery = window.eviivo.jQuery || {};

window.eviivo.jQuery.textFade = (function($) {

    $.fn.textFade = function(newText, options) {
        if (typeof(newText) !== "string") {
            throw "newText must be a string";
        }

        options = options || {};

        var duration = (function() {
            if (options.duration === undefined) {
                return 100;
            }

            var type = typeof (options.duration);
            if (type === "string") {
                if (options.duration !== "fast" && options.duration !== "slow") {
                    throw "Invalid value for options.duration";
                }

                return options.duration;
            }

            if (type === "number") {
                if (options.duration < 0) {
                    throw "Invalid value for options.duration";
                }

                return options.duration;
            }

            throw "Invalid value for options.duration";
        })();

        var beforeShow = (function() {
            if (options.beforeShow === undefined) {
                return function() {};
            }

            if (typeof(options.beforeShow) !== "function") {
                throw "options.beforeShow must be a function";
            }

            return options.beforeShow;
        })();

        var complete = (function() {
            if (options.complete === undefined) {
                return function () { };
            }

            if (typeof (options.complete) !== "function") {
                throw "options.complete must be a function";
            }

            return options.complete;
        })();

        this.each(function() {
            var $elem = $(this);

            $elem.animate({ opacity: 0 }, { duration: duration, complete: function() {
                    $elem.text(newText);

                    beforeShow.call($elem.get(0));

                    $elem.animate({ opacity: 1 }, { duration: duration, complete: complete });
                }
            });
        });
    }


})(window.jQuery);
"use strict";

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};

window.eviivo.availabilityCalendar.utils = (function() {

    var intRegex = /^[0-9]+$/;

    var dateRegex = /^(\d\d\d\d)-(0[0-9]|1[012])-([012][0-9]|3[01])$/;
    var roomTypeIdRegex = /^\d+$/;
    var ratePlanIdRegex = /^\d+$/;
    
    function parseDate(dateStr) {
        var match = dateRegex.exec(dateStr);
        if (!match) {
            return undefined;
        }

        return new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]));
    }
    
    var dateNumberLookup = ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31"];
    function toDateString(date) {

        var month = date.getMonth() + 1;
        var monthStr = dateNumberLookup[month];

        var dateInMonth = date.getDate();
        var dayStr = dateNumberLookup[dateInMonth];

        return date.getFullYear() + "-" + monthStr + "-" + dayStr;
    }

    function isValidRoomTypeId(value) { return roomTypeIdRegex.test(value); }
    function isValidRatePlanId(value) { return ratePlanIdRegex.test(value); }

    function isValidRoomTypeIdArray(roomTypes) {

        if (!roomTypes) {
            return false;
        }

        if (!Array.isArray(roomTypes)) {
            return false;
        }

        for (var i = 0; i < roomTypes.length; i++) {
            if (!isValidRoomTypeId(roomTypes[i])) {
                return false;
            }
        }

        return true;
    }

    function isValidOccupancyValue(occupancy) {
        if (!occupancy) {
            return false;
        }

        if (typeof(occupancy) !== "object") {
            return false;
        }

        if (!setEqual(["adults", "children"], Object.getOwnPropertyNames(occupancy))) {
            return false;
        }

        if (typeof(occupancy.adults) !== "number") {
            return false;
        }

        if (typeof (occupancy.children) !== "number") {
            return false;
        }

        return true;
    }

    function isValidOccupanciesArray(occupancies) {
        if (!occupancies) {
            return false;
        }

        if (!Array.isArray(occupancies)) {
            return false;
        }

        for (var i = 0; i < occupancies.length; i++) {
            if (!isValidOccupancyValue(occupancies[i])) {
                return false;
            }
        }

        return true;
    }

    var whitespaceRegex = /^\W*$/;
    function isUndefNullOrWhitespace(str) {
        if (str === undefined) {
            return true;
        }

        if (str === null) {
            return true;
        }

        if (typeof(str) !== "string") {
            return true;
        }

        if (whitespaceRegex.test(str)) {
            return true;
        }

        return false;
    }

    function addDays(date, days) {
        var newDate = parseDate(date);
        newDate.setDate(newDate.getDate() + days);
        return toDateString(newDate);
    }

    function addMonths(date, months) {
        var newDate = parseDate(date);
        newDate.setMonth(newDate.getMonth() + months);
        return toDateString(newDate);
    }

    function endOfMonth(date) {
        var inDate = parseDate(date);
        var newDate = new Date(inDate.getFullYear(), inDate.getMonth() + 1, 0);
        return toDateString(newDate);
    }

    function startOfMonth(date) {
        var inDate = parseDate(date);
        var newDate = new Date(inDate.getFullYear(), inDate.getMonth(), 1);
        return toDateString(newDate);
    }

    function nightsBetween(startDate, endDate) {
        if (!dateRegex.test(startDate)) {
            throw "startDate is not in valid date format";
        }

        if (!dateRegex.test(endDate)) {
            throw "endDate is not in valid date format";
        }

        if (endDate < startDate) {
            throw "endDate must be after startDate";
        }

        var endDateObj = parseDate(endDate);

        var count = 0;
        for (var dateObj = parseDate(startDate) ; !_isSameDay(dateObj, endDateObj) ; dateObj.setDate(dateObj.getDate() + 1)) {
            count++;
        }

        return count;
    }

    function _isSameDay(dateA, dateB) {
        return dateA.getFullYear() === dateB.getFullYear() &&
            dateA.getMonth() === dateB.getMonth() &&
            dateA.getDate() === dateB.getDate();
    }

    function forEachDayBetween(startDate, endDate, func) {
        var startDateObj = parseDate(startDate);
        var endDateObj = parseDate(endDate);

        if (startDateObj === undefined) {
            throw "startDate must be a valid date";
        }

        if (endDateObj === undefined) {
            throw "endDate must be a valid date";
        }

        if (endDate < startDate) {
            throw "endDate must not be before startDate";
        }

        for (startDateObj; !_isSameDay(startDateObj, endDateObj); startDateObj.setDate(startDateObj.getDate() + 1)) {
            var ret = func(toDateString(startDateObj));
            if (ret === false) {
                return;
            }
        }

        func(endDate);
    }

    function today() { return toDateString(new Date()); }

    function all(arr, predicate) {
        if (!arr || !Array.isArray(arr)) {
            throw "arr must be an array";
        }

        if (!predicate || typeof(predicate) !== "function") {
            throw "predicate must be a function";
        }

        if (predicate.length !== 1) {
            throw "predicate must only take 1 argument";
        }

        for (var i = 0; i < arr.length; i++) {
            if (!predicate(arr[i])) {
                return false;
            }
        }

        return true;
    }

    function any(arr, predicate) {
        if (!arr || !Array.isArray(arr)) {
            throw "arr must be an array";
        }

        if (!predicate || typeof(predicate) !== "function") {
            throw "predicate must be a function";
        }

        if (predicate.length !== 1) {
            throw "predicate must only take 1 argument";
        }

        for (var i = 0; i < arr.length; i++) {
            if (predicate(arr[i])) {
                return true;
            }
        }

        return false;
    }

    function where(arr, predicate) {
        if (!arr || !Array.isArray(arr)) {
            throw "arr must be an array";
        }

        if (!predicate || typeof (predicate) !== "function") {
            throw "predicate must be a function";
        }

        if (predicate.length !== 1) {
            throw "predicate must only take 1 argument";
        }

        // This isn't particularly efficient, but a lack of underscore/lodash
        // means it's more important to make this clear than performant

        var newArray = [];

        for (var i = 0; i < arr.length; i++) {
            if (predicate(arr[i])) {
                newArray.push(arr[i]);
            }
        }

        return newArray;
    }

    function single(arr, predicate) {
        if (!arr || !Array.isArray(arr)) {
            throw "arr must be an array";
        }

        if (!predicate || typeof (predicate) !== "function") {
            throw "predicate must be a function";
        }

        if (predicate.length !== 1) {
            throw "predicate must only take 1 argument";
        }

        var value;
        var foundValue = false;

        for (var i = 0; i < arr.length; i++) {
            if (predicate(arr[i])) {
                if (foundValue) {
                    throw "More than one value in the array satisfies the predicate";
                }

                value = arr[i];
                foundValue = true;
            }
        }

        if (foundValue === false) {
            throw "No value in the array satisfies the predicate";
        }

        return value;
    }

    function firstOrDefault(arr, predicate) {
        if (!arr || !Array.isArray(arr)) {
            throw "arr must be an array";
        }

        if (!predicate || typeof (predicate) !== "function") {
            throw "predicate must be a function";
        }

        if (predicate.length !== 1) {
            throw "predicate must only take 1 argument";
        }
        
        for (var i = 0; i < arr.length; i++) {
            if (predicate(arr[i])) {
                return arr[i];
            }
        }

        return undefined;
    }

    function intersect(arr1, arr2) {
        if (!Array.isArray(arr1)) {
            throw "arr1 is not an array";
        }

        if (!Array.isArray(arr2)) {
            throw "arr2 is not an array";
        }

        var left, right;
        if (arr1.length <= arr2.length) {
            left = arr1;
            right = arr2;
        } else {
            left = arr2;
            right = arr1;
        }

        var intersection = [];
        for (var i = 0; i < left.length; i++) {
            for (var j = 0; j < right.length; j++) {
                if (left[i] === right[j]) {
                    intersection.push(left[i]);
                    break;
                }
            }
        }

        return intersection;
    }

    function setEqual(arr1, arr2) {
        if (!Array.isArray(arr1)) {
            throw "arr1 is not an array";
        }

        if (!Array.isArray(arr2)) {
            throw "arr2 is not an array";
        }

        if (arr1.length !== arr2.length) {
            return false;
        }

        for (var i = 0; i < arr1.length; i++) {
            var found = false;
            for (var j = 0; j < arr2.length; j++) {
                if (arr1[i] === arr2[j]) {
                    found = true;
                    break;
                }
            }

            if (!found) {
                return false;
            }
        }

        return true;
    }

    function setDifference(arr1, arr2) {
        if (!Array.isArray(arr1)) {
            throw "arr1 must be an array";
        }

        if (!Array.isArray(arr2)) {
            throw "arr2 must be an array";
        }

        // This is horribly inefficient, but considering
        // the small sizes of sets I expect this to run on,
        // a O(m*n) run time doesn't evaluate to a particularly
        // troublesome amount

        var result = [];
        for (var i = 0; i < arr1.length; i++) {
            var present = false;
            for (var j = 0; j < arr2.length; j++) {
                if (arr1[i] === arr2[j]) {
                    present = true;
                    break;
                }
            }

            if (!present) {
                result.push(arr1[i]);
            }
        }

        return result;
    }

    function isSubsetOf(testSet, superSet) {
        if (!Array.isArray(testSet)) {
            throw "testSet must be an array";
        }

        if (!Array.isArray(superSet)) {
            throw "superSet must be an array";
        }

        for (var i = 0; i < testSet.length; i++) {
            var contains = false;
            for (var j = 0; j < superSet.length; j++) {
                if (testSet[i] === superSet[j]) {
                    contains = true;
                    break;
                }
            }

            if (!contains) {
                return false;
            }
        }

        return true;
    }

    function isOneOf(testValue) {
        
        if (typeof(testValue) === "string") {

            for (var i = 1; i < arguments.length; i++) {
                if (arguments[i] === testValue) {
                    return true;
                } else if (arguments[i] && arguments[i].test && arguments[i].test(testValue)) {
                    return true;
                }
            }

        } else {

            for (var i = 1; i < arguments.length; i++) {
                if (arguments[i] === testValue) {
                    return true;
                }
            }

        }

        return false;
    }

    function countUntil(arr, valueOrPredicate) {
        if (!arr || !Array.isArray(arr)) {
            throw "arr must be an array";
        }

        if (typeof (valueOrPredicate) === "function") {
            var predicate = valueOrPredicate;
            if (predicate.length !== 1) {
                throw "predicate must only take 1 argument";
            }

            for (var i = 0; i < arr.length; i++) {
                if (predicate(arr[i])) {
                    return i;
                }
            }

            return arr.length;
        } else {
            var value = valueOrPredicate;

            for (var i = 0; i < arr.length; i++) {
                if (arr[i] === value) {
                    return i;
                }
            }

            return arr.length;
        }
    }

    function clampAbove(clampValue) {
        return function(value) {
            return value > clampValue
                ? clampValue
                : value;
        }
    }

    function clampBelow(clampValue) {
        return function(value) {
            return value < clampValue
                ? clampValue
                : value;
        }
    }

    function hasCount(arr, count, predicate) {
        if (!Array.isArray(arr)) {
            throw "arr is not an array";
        }

        if (typeof(count) !== "number") {
            throw "count is not a number";
        }

        if (typeof (predicate) !== "function" && predicate.length !== 1) {
            throw "predicate must be a function accepting one argument";
        }

        var foundCount = 0;
        for (var i = 0; i < arr.length; i++) {
            if (predicate(arr[i])) {
                foundCount++;

                if (foundCount > count) {
                    return false;
                }
            }
        }

        return foundCount === count;
    }

    function $hasCount($elems, count, predicate) {
        if (!($elems instanceof jQuery)) {
            throw "$elems must be a jQuery object";
        }

        if (typeof (count) !== "number") {
            throw "count is not a number";
        }

        if (typeof (predicate) !== "function" && predicate.length !== 1) {
            throw "predicate must be a function accepting one argument";
        }

        var foundCount = 0;
        $elems.each(function(_, elem) {
            if (predicate(elem)) {
                foundCount++;

                if (foundCount > count) {
                    return false;
                }
            }
        });

        return foundCount === count;
    }

    function max(arr, maxOfFunc) {
        if (!Array.isArray(arr)) {
            throw "arr is not an array";
        }

        if (arr.length === 0) {
            throw "arr has no items";
        }

        var maxOf = maxOfFunc || function (a, b) { return a > b ? a : b; };
        var currentMax = arr[0];
        for (var i = 1; i < arr.length; i++) {
            currentMax = maxOf(currentMax, arr[i]);
        }

        return currentMax;
    }

    function sum(arr, strOrFunc) {
        if (!Array.isArray(arr)) {
            throw "arr must be an array";
        }

        if (arr.length === 0) {
            return 0;
        }

        var type = typeof (strOrFunc);
        var accessor;
        if (type === "undefined") {
            if (!isArrayOf("number")(arr)) {
                throw "If strOrFunc is not defined, the array must only contain numbers";
            }

            accessor = identity;
        } else if (type === "string") {
            for (var i = 0; i < arr.length; i++) {
                if (!arr[i].hasOwnProperty(strOrFunc)) {
                    throw "Not all objects in the array have the property " + strOrFunc;
                }
            }

            accessor = function (val) { return val[strOrFunc]; };
        } else if (type === "function" && strOrFunc.length === 1) {
            accessor = strOrFunc;
        } else {
            throw "strOrFunc must be undefined, a string or a function taking 1 parameter";
        }

        var total = 0;
        for (var i = 0; i < arr.length; i++) {
            var val = accessor(arr[i]);
            if (typeof(val) !== "number") {
                throw "Result of transform of element `" + i + "` is not a number";
            }

            total += val;
        }

        return total;
    }

    function map(arr, mapStrOrFunc) {
        if (!Array.isArray(arr)) {
            throw "arr must be an array";
        }

        var mapType = typeof (mapStrOrFunc);
        if (mapType !== "string" && mapType !== "function") {
            throw "mapStrOrFunc must be a string or a function";
        }

        var func = mapType === "function"
            ? mapStrOrFunc
            : function (val) { return val[mapStrOrFunc]; };

        var newArr = new Array(arr.length);
        for (var i = 0; i < newArr.length; i++) {
            newArr[i] = func(arr[i], i);
        }

        return newArr;
    }

    function mapMany(arr, func) {
        if (!Array.isArray(arr)) {
            throw "arr must be an array";
        }

        if (typeof(func) !== "function" && func.length !== 1) {
            throw "func must be a function taking 1 argument";
        }

        var newArr = new Array();
        for (var i = 0; i < arr.length; i++) {
            var value = func(arr[i]);
            if (!Array.isArray(value)) {
                throw "func did not return an array to flatten";
            }

            for (var j = 0; j < value.length; j++) {
                newArr.push(value[j]);
            }
        }

        return newArr;
    }

    function mapValues(obj, transform) {
        if (obj === undefined || obj === null || typeof(obj) !== "object") {
            throw "obj must be an object";
        }

        if (typeof(transform) !== "function" && transform.length !== 1) {
            throw "transform must be a function taking one parameter";
        }

        var returnObj = {};

        var keys = Object.getOwnPropertyNames(obj);
        for (var i = 0; i < keys.length; i++) {
            returnObj[keys[i]] = transform(obj[keys[i]]);
        }

        return returnObj;
    }

    function range(start, end) {
        if (typeof(start) !== "number") {
            throw "start must be a number";
        }

        if (typeof(end) !== "number") {
            throw "end must be a number";
        }

        if (start < end) {
            var values = new Array((end - start) + 1);
            for (var i = 0; start + i <= end; i++) {
                values[i] = start + i;
            }

            return values;
        } else if (start > end) {
            var values = new Array((start - end) + 1);
            for (var i = 0; start - i >= end; i++) {
                values[i] = start - i;
            }

            return values;
        } else {
            return [start];
        }
    }

    function numericSort(arr) {
        if (!Array.isArray(arr)) {
            throw "arr must be an array";
        }

        var sortedArray = new Array(arr.length);
        for (var i = 0; i < arr.length; i++) {
            if (typeof(arr[i]) !== "number") {
                throw "arr contains an element that is not a number";
            }

            sortedArray[i] = arr[i];
        }

        sortedArray.sort(function (a, b) { return a - b; });

        return sortedArray;
    }

    var notCache = {};
    function not(func) {
        if (typeof(func) !== "function") {
            throw "func must be a function";
        }

        if (notCache[func.length]) {
            return notCache[func.length](func);
        }

        var argArray = new Array(func.length);
        for (var i = 0; i < func.length; i++) {
            argArray[i] = "arg" + i;
        }
        
        var body = "return function(" + argArray.join(", ") + ") { return !func(" + argArray.join(", ") + "); };";
        var generator = new Function("func", body);

        notCache[func.length] = generator;

        return generator(func);
    }

    function deepClone(obj) {
        if (obj === undefined || obj === null) {
            return obj;
        }

        var type = typeof (obj);

        if (type === "boolean" || type === "number" || type === "string" || type === "function") {
            return obj;
        }

        if (obj instanceof Date) {
            return new Date(obj.value);
        }

        if (Array.isArray(obj)) {
            var arrClone = new Array(obj.length);
            for (var i = 0; i < arrClone.length; i++) {
                arrClone[i] = deepClone(obj[i]);
            }

            return arrClone;
        }

        if (type === "object") {
            var objClone = {};
            for (var prop in obj) {
                if (obj.hasOwnProperty(prop)) {
                    objClone[prop] = deepClone(obj[prop]);
                }
            }

            return objClone;
        }

        throw "Cannot clone this type of object";
    }

    function immutableDeepClone(obj) {
        if (obj === undefined || obj === null) {
            return obj;
        }

        var type = typeof (obj);

        if (type === "boolean" || type === "number" || type === "string" || type === "function") {
            return obj;
        }

        if (obj instanceof Date) {
            return new Date(obj.value);
        }

        if (Array.isArray(obj)) {
            var arrClone = new Array(obj.length);
            for (var i = 0; i < arrClone.length; i++) {
                arrClone[i] = immutableDeepClone(obj[i]);
            }

            return Object.freeze(arrClone);
        }

        if (type === "object") {
            var objClone = {};
            for (var prop in obj) {
                if (obj.hasOwnProperty(prop)) {
                    objClone[prop] = immutableDeepClone(obj[prop]);
                }
            }

            return Object.freeze(objClone);
        }

        throw "Cannot clone this type of object";
    }

    function readOnlyWrapper(obj) {
        if (typeof (obj) !== "object" || Array.isArray(obj)) {
            throw "Must be an object";
        }

        var getPropFunc = function(p) { return this[p]; };

        var propDefinitions = {};
        for (var prop in obj) {
            if (obj.hasOwnProperty(prop)) {
                propDefinitions[prop] = readOnlyDescriptor(getPropFunc.bind(obj, prop));
            }
        }

        return Object.freeze(Object.create(null, propDefinitions));
    }

    function readOnlyDescriptor(getter) {
        if (typeof(getter) === "function") {
            return {
                configurable: false,
                enumerable: true,
                writeable: false,
                get: getter
            };
        } else {
            return {
                configurable: false,
                enumerable: true,
                writeable: false,
                value: getter
            };
        }
    }

    function readOnlyValueDescriptor(value) {
        return {
            configurable: false,
            enumerable: true,
            writeable: false,
            value: value
        };
    }

    function format(formatStr, obj) {
        var replaceRegex = new RegExp("{(" + Object.keys(obj).join("|") + ")}", "g");
        return formatStr.replace(replaceRegex, function(_, key) { return obj[key]; });
    }

    function elemContainsOrEqual(elemContainer, elemContainee) {
        if (elemContainer instanceof jQuery) {
            return $.grep(elemContainer, function (el) {
                return $(elemContainee).is(el) || $.contains(el, elemContainee);
                }).length > 0;
        } else if (elemContainer.nodeType === 1) { // DOM Element
            return $(elemContainee).is(elemContainer) && !$.contains(elemContainer, elemContainee);
        } else {
            throw "elem must be a jQuery object or a DOM element";
        }
    }

    function onKey(keys, func) {

        if (typeof(keys) !== "number" && (!Array.isArray(keys) || all(keys, function(k) { return typeof (k) === "number"; }))) {
            throw "keys must be a single number or an array of numbers";
        }
            
        return function(e) {
            if (Array.isArray(keys) && any(keys, function(k) { return k === e.which; })) {
                func.apply(this, arguments);
            } else if (typeof (keys) === "number" && keys === e.which) {
                func.apply(this, arguments);
            }
        }
    }

    function clickOutsideOf() { // (elem[, elem2, elem3, ...], func)
        var func = arguments[arguments.length - 1];
        if (typeof(func) !== "function") {
            throw "Last argument must be the event handler function to call";
        }

        var elems = new Array(arguments.length - 1);
        for (var i = 0; i < arguments.length - 1; i++) {
            if (!(arguments[i] instanceof jQuery) && !(arguments[i].nodeType === 1)) {
                throw "All provided elements must be a jQuery object or a DOM element";
            }

            elems[i] = arguments[i];
        }
            
        return function(event) {
            if (all(elems, function(elem) { return !elemContainsOrEqual(elem, event.target); })) {
                func.apply(this, arguments);
            }
        }
    }

    function getOwnPropertyDescriptors(obj) {
        if (Object.getOwnPropertyDescriptors !== undefined) {
            return Object.getOwnPropertyDescriptors(obj);
        }

        var propertyNames = Object.getOwnPropertyNames(obj);
        var descriptors = {};
        for (var i = 0; i < propertyNames.length; i++) {
            descriptors[propertyNames[i]] = Object.getOwnPropertyDescriptor(obj, propertyNames[i]);
        }

        return descriptors;
    }

    function startsWith(string, testStr) {
        if (string === undefined || string === null) { return false; }
        return string.indexOf(testStr) === 0;
    }

    function identity(i) { return i; }

    function prefixAll(prefix, strArr) {
        if (!Array.isArray(strArr) || !all(strArr, function(elem) { return typeof (elem) === "string"; })) {
            throw "strArr must be an array of strings";
        }

        if (typeof (prefix) !== "string") {
            throw "prefix must be a string";
        }

        var newArr = new Array(strArr.length);
        for (var i = 0; i < strArr.length; i++) {
            newArr[i] = prefix + strArr[i];
        }

        return newArr;
    }

    function $elemSummary($elem) {
        if (!($elem instanceof jQuery)) {
            throw "$elem must be a jQuery object";
        }

        if ($elem.length !== 1) {
            throw "$elem must represent a single element";
        }

        var tagName = $elem.prop('tagName');
        var classes = prefixAll('.', $elem.prop('class').split(/\s+/));

        return tagName + classes.join('');
    }

    function splitIntoHtmlLines(str, delimiter) {
        var $tempSpan = $("<span></span>");
        return $.map(str.split(delimiter),
                function (part) {
                    return $tempSpan.text(part.trim()).html();
                })
            .join("<br />");
    }

    function isArrayOf(typeStr) {
        return function(arr) {
            if (!Array.isArray(arr)) {
                return false;
            }

            for (var i = 0; i < arr.length; i++) {
                if (typeof(arr[i]) !== typeStr) {
                    return false;
                }
            }

            return true;
        }
    }

    function cssClassStringToArray(str) {
        if (typeof(str) !== "string") {
            throw "str must be a string";
        }

        return where(str.split(" "), not(isUndefNullOrWhitespace));
    }

    return Object.freeze({
        
        isValidRoomTypeId: isValidRoomTypeId,
        isValidRatePlanId: isValidRatePlanId,
        isValidRoomTypeIdArray: isValidRoomTypeIdArray,

        isValidOccupancyValue: isValidOccupancyValue,
        isValidOccupanciesArray: isValidOccupanciesArray,

        intRegex: intRegex,

        isUndefNullOrWhitespace: isUndefNullOrWhitespace,

        dateRegex: dateRegex,
        addDays: addDays,
        addMonths: addMonths,
        endOfMonth: endOfMonth,
        startOfMonth: startOfMonth,
        nightsBetween: nightsBetween,
        forEachDayBetween: forEachDayBetween,
        today: today,
        toDateString: toDateString,
        parseDate: parseDate,

        all: all,
        any: any,
        where: where,
        single: single,
        firstOrDefault: firstOrDefault,
        intersect: intersect,
        setEqual: setEqual,
        setDifference: setDifference,
        isSubsetOf: isSubsetOf,
        isOneOf: isOneOf,
        countUntil: countUntil,
        clampAbove: clampAbove,
        clampBelow: clampBelow,
        hasCount: hasCount,
        $hasCount: $hasCount,
        max: max,
        sum : sum,
        map: map,
        mapMany: mapMany,
        mapValues: mapValues,
        range: range,
        numericSort: numericSort,

        not: not,

        deepClone: deepClone,
        immutableDeepClone: immutableDeepClone,

        readOnlyWrapper: readOnlyWrapper,
        readOnlyDescriptor: readOnlyDescriptor,
        readOnlyValueDescriptor: readOnlyValueDescriptor,

        format: format,

        onKey: onKey,
        clickOutsideOf: clickOutsideOf,

        getOwnPropertyDescriptors: getOwnPropertyDescriptors,

        startsWith: startsWith,

        identity: identity,

        MAX_SAFE_INTEGER: 9007199254740991,

        prefixAll: prefixAll,

        $elemSummary: $elemSummary,

        splitIntoHtmlLines: splitIntoHtmlLines,

        isArrayOf: isArrayOf,

        cssClassStringToArray: cssClassStringToArray
    });
})();
"use strict";

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};
window.eviivo.availabilityCalendar.ui = window.eviivo.availabilityCalendar.ui || {};

window.eviivo.availabilityCalendar.ui.urlHandler = (function (utils, queryStringHelper) {

    var adultsKey = /^adults([0-9]+)$/;
    var childrenKey = /^children([0-9]+)$/;
    var roomFilterKey = /^roomtype([0-9]+)$/;

    function generateNewUrlFromCriteria(currentQueryString, criteria, customReferrerShortName) {
        var queryParts = {
            noofrooms: criteria.occupancy.length,
            ref: customReferrerShortName
        };

        if (criteria.startDate !== null) {
            queryParts.startDate = criteria.startDate;
        }

        if (criteria.endDate !== null) {
            queryParts.endDate = criteria.endDate;
        }

        var urlParams = new URLSearchParams(currentQueryString);
        if (urlParams.has('gclid')) {
            queryParts.gclid = urlParams.get('gclid');
        }              

        //see if promo code has to be submited fresh
        var promotionCodeElement = $("#pce");
        if (promotionCodeElement.length > 0) {
            queryParts.pce = promotionCodeElement.val();
        }

        //see if promo instant deal has to be submited fresh
        var promotionEnableDealsElement = $("#peid");
        if (promotionEnableDealsElement.length > 0 && promotionEnableDealsElement.val() != null && promotionEnableDealsElement.val() !== "") {
            queryParts.peid = promotionEnableDealsElement.val();
        }


        var newQueryParts = $.extend({}, queryParts, queryPartsForOccupancy(criteria.occupancy), queryPartsForRoomFilter(criteria.roomFilter));

        var oldQueryString = queryStringHelper.getAllKeys(currentQueryString);
        var oldQueryParts = {};

        // Remove all keys that we care about so we can guarantee a valid state afterwards
        for (var key in oldQueryString) {
            if (Object.hasOwnProperty(oldQueryString, key) && !utils.isOneOf(key, adultsKey, childrenKey, "startdate", "enddate", "noofrooms", "nights", "roomcount", "pce", "peid")) {
                oldQueryParts[key] = oldQueryString[key];
            }
        }

        var newQueryString = Object.freeze($.extend({}, oldQueryParts, newQueryParts));
        return queryStringHelper.toString(newQueryString, true);
    }



    function queryPartsForOccupancy(occupancy) {
        var queryParts = {};
        for (var i = 0; i < occupancy.length; i++) {
            queryParts["adults" + (i + 1)] = occupancy[i].adults.toString();
            queryParts["children" + (i + 1)] = occupancy[i].children.toString();
        }

        return queryParts;
    }

    function queryPartsForRoomFilter(roomFilter) {
        if (roomFilter === undefined) {
            return {};
        }

        var queryParts = {};
        for (var i = 0; i < roomFilter.length; i++) {
            queryParts["roomtype" + (i + 1)] = roomFilter[i].toString();
        }

        return queryParts;
    }

    function extractCriteriaFromUrl(queryString) {
        var qsValues = queryStringHelper.getAllKeys(queryString);

        var startDate = utils.dateRegex.test(qsValues.startdate)
            ? qsValues.startdate
            : null;

        var endDate = startDate !== null && utils.dateRegex.test(qsValues.enddate)
            ? qsValues.enddate
            : null;

        //in case we had number of nights from legacy URL formats
        if (startDate !== null && utils.intRegex.test(qsValues.nights)) {
            var nights = parseInt(qsValues.nights);
            endDate = utils.addDays(startDate, nights);
        }

        var roomCount = (function () {
            if (utils.intRegex.test(qsValues.noofrooms)) {
                return parseInt(qsValues.noofrooms);
            }

            var keys = $.grep(Object.getOwnPropertyNames(qsValues), function (key) {
                return adultsKey.test(key) || childrenKey.test(key);
            });

            if (keys.length === 0) {
                return 0;
            }

            return utils.max($.map(keys, function (key) {
                var adultsMatch = adultsKey.exec(key);
                if (adultsMatch !== null) {
                    return parseInt(adultsMatch[1]);
                }

                var childrenMatch = childrenKey.exec(key);
                if (childrenMatch !== null) {
                    return parseInt(childrenMatch[1]);
                }

                throw "Unknown algorithm state...";
            }));
        })();

        var occupancy = (function () {
            if (roomCount < 1) {
                return [{ adults: 2, children: 0 }];
            }

            var occupancy = [];
            for (var i = 1; i <= roomCount; i++) {
                var adults = utils.intRegex.test(qsValues["adults" + i])
                    ? parseInt(qsValues["adults" + i])
                    : 0;

                var children = utils.intRegex.test(qsValues["children" + i])
                    ? parseInt(qsValues["children" + i])
                    : 0;

                if (adults === 0 && children === 0) {
                    continue;
                }

                occupancy.push({ adults: adults, children: children });
            }

            return occupancy;
        })();

        var roomFilters = (function () {
            var keys = $.grep(Object.getOwnPropertyNames(qsValues), function (key) { return roomFilterKey.test(key); });

            if (keys.length === 0) {
                return undefined;
            }

            var roomFilters = [];
            for (var i = 0; i < keys.length; i++) {
                if (utils.intRegex.test(qsValues[keys[i]])) {
                    roomFilters.push(parseInt(qsValues[keys[i]]));
                }
            }

            return roomFilters;
        })();

        return Object.freeze({
            startDate: startDate,
            endDate: endDate,
            occupancy: occupancy,
            roomFilter: roomFilters
        });
    }

    return Object.freeze({
        extractCriteriaFromUrl: extractCriteriaFromUrl,
        generateNewUrlFromCriteria: generateNewUrlFromCriteria
    });
})(eviivo.availabilityCalendar.utils, eviivo.utils.queryStringHelper);
"use strict";

window.eviivo = window.eviivo || {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};

window.eviivo.availabilityCalendar.data = (function (utils, urlHandler) {

    var defaultAvailability = Object.freeze({
        MaxLengthOfStay: utils.MAX_SAFE_INTEGER,
        MinLengthOfStay: 0,
        CanArrive: false,
        CanDepart: false,
        CanContinueStay: false
    });

    return function(availabilityData, occupancyData) {

        var dataChangeEvent = (function() {
            var listeners = [];

            function attachListener(func) {
                if (typeof(func) !== "function") {
                    throw "func must be a function";
                }

                listeners.push(func);
            }

            function trigger() {
                for (var i = 0; i < listeners.length; i++) {
                    listeners[i].call(null);
                }
            }

            return Object.freeze({
                attachListener: attachListener,
                trigger: trigger
            });
        })();

        availabilityData = availabilityData || {};
        var availability = (function(initialData, initialEarliestDate, initialFurthestDate) {

            var availabilityData = {};

            var furthestDateLoaded = utils.toDateString(new Date(Number.MIN_VALUE));
            var earliestDateLoaded = utils.toDateString(new Date(Number.MAX_VALUE));

            if (initialData && initialEarliestDate && initialFurthestDate) {
                insertAvailabilityData(initialEarliestDate, initialFurthestDate, initialData);
            }

            function validateData(data) {
                if (!data || typeof(data) !== "object") {
                    return false;
                }

                var validProperties = Object.getOwnPropertyNames(defaultAvailability);

                for (var date in data) {
                    if (!utils.dateRegex.test(date)) {
                        return false;
                    }

                    var roomTypes = data[date];
                    if (!roomTypes || typeof(roomTypes) !== "object") {
                        return false;
                    }

                    for (var roomType in roomTypes) {
                        if (!utils.isValidRoomTypeId(roomType)) {
                            return false;
                        }

                        var ratePlans = roomTypes[roomType];
                        if (!ratePlans || typeof (ratePlans) !== "object") {
                            return false;
                        }

                        for (var ratePlan in ratePlans) {
                            if (!utils.isValidRatePlanId(ratePlan)) {
                                return false;
                            }

                            var availabilityInfo = ratePlans[ratePlan];
                            for (var prop in availabilityInfo) {
                                if (validProperties.indexOf(prop) === -1) {
                                    return false;
                                }
                            }
                        }
                    }
                }

                return true;
            }

            function insertAvailabilityData(earliestDate, furthestDate, data) {

                if (!utils.dateRegex.test(earliestDate)) {
                    throw "earliestDate is not a valid ISO8601 extended date format";
                }

                if (!utils.dateRegex.test(furthestDate)) {
                    throw "furthestDate is not a valid ISO8601 extended date format";
                }

                if (!validateData(data)) {
                    throw "data is not in a valid form";
                }

                var propertiesToCopy = Object.getOwnPropertyNames(defaultAvailability);

                for (var date in data) {
                    for (var roomType in data[date]) {
                        for (var ratePlan in data[date][roomType]) {

                            var incomingAvailabilityData = data[date][roomType][ratePlan];
                            var availabilityDataCopy = {};
                            for (var i = 0; i < propertiesToCopy.length; i++) {
                                var prop = propertiesToCopy[i];
                                availabilityDataCopy[prop] =
                                    incomingAvailabilityData[prop] || defaultAvailability[prop];
                            }

                            availabilityData[date] = availabilityData[date] || {};
                            availabilityData[date][roomType] = availabilityData[date][roomType] || {};
                            availabilityData[date][roomType][ratePlan] =
                                availabilityData[date][roomType][ratePlan] || {};
                            availabilityData[date][roomType][ratePlan] = Object.freeze(availabilityDataCopy);
                        }
                    }
                }

                if (earliestDate < earliestDateLoaded) {
                    earliestDateLoaded = earliestDate;
                }

                if (furthestDate > furthestDateLoaded) {
                    furthestDateLoaded = furthestDate;
                }

                dataChangeEvent.trigger();
            }

            function getDates() {
                return Object.getOwnPropertyNames(availabilityData);
            }

            function getRoomTypes(date) {
                if (!utils.dateRegex.test(date)) {
                    throw "Invalid date provided";
                }

                if (date < earliestDateLoaded || date > furthestDateLoaded) {
                    throw "Date is beyond loaded data";
                }

                var roomTypes = availabilityData[date];
                return roomTypes ? Object.getOwnPropertyNames(roomTypes) : [];
            }

            function getRatePlans(date, roomType) {
                if (!utils.dateRegex.test(date)) {
                    throw "Invalid date provided";
                }

                if (!utils.isValidRoomTypeId(roomType)) {
                    throw "Invalid room type provided";
                }

                if (date < earliestDateLoaded || date > furthestDateLoaded) {
                    throw "Date is beyond loaded data";
                }

                var roomTypes = availabilityData[date];
                if (!roomTypes) {
                    return [];
                }

                var ratePlans = roomTypes[roomType];
                return ratePlans ? Object.getOwnPropertyNames(ratePlans) : [];
            }

            function getAvailabilityInfo(date, roomType, ratePlan) {

                if (!utils.dateRegex.test(date)) {
                    throw "Invalid date provided";
                }

                if (!utils.isValidRoomTypeId(roomType)) {
                    throw "Invalid room type provided";
                }

                if (!utils.isValidRatePlanId(ratePlan)) {
                    throw "Invalid rate plan provided";
                }

                if (date < earliestDateLoaded || date > furthestDateLoaded) {
                    throw "Date is beyond loaded data";
                }

                var roomTypes = availabilityData[date];
                if (!roomTypes) {
                    return defaultAvailability;
                }

                var ratePlans = roomTypes[roomType];
                if (!ratePlans) {
                    return defaultAvailability;
                }

                var availabilityInfo = ratePlans[ratePlan];
                return availabilityInfo || defaultAvailability;
            }

            function forceEarliestDate(date) {
                if (!utils.dateRegex.test(date)) {
                    throw "Invalid date provided";
                }

                earliestDateLoaded = date;
                for (var d in availabilityData) {
                    if (d < earliestDateLoaded) {
                        delete availabilityData[d];
                    }
                }

                dataChangeEvent.trigger();
            }

            function forceFurthestDate(date) {
                if (!utils.dateRegex.test(date)) {
                    throw "Invalid date provided";
                }

                furthestDateLoaded = date;
                for (var d in availabilityData) {
                    if (d > furthestDateLoaded) {
                        delete availabilityData[d];
                    }
                }

                dataChangeEvent.trigger();
            }

            return Object.freeze({
                insertAvailabilityData: insertAvailabilityData,
                get furthestDateLoaded() { return furthestDateLoaded; },
                get earliestDateLoaded() { return earliestDateLoaded; },

                getDates: getDates,
                getRoomTypes: getRoomTypes,
                getRatePlans: getRatePlans,
                getAvailabilityInfo: getAvailabilityInfo,

                forceEarliestDate: forceEarliestDate,
                forceFurthestDate: forceFurthestDate
            });
        })(availabilityData.initialData, availabilityData.initialEarliestDate, availabilityData.initialFurthestDate);

        occupancyData = occupancyData || {};
        var occupancy = (function(occupancyBootstrap, maxInventory) {

            var occupancyData;
            if (typeof maxInventory == 'undefined') {
                //NewtonSoftJSON optimizes the request, therefore int values with default 0 disappear from the request. this makes sure the values is back to 0
                maxInventory = 0;
            }

            if (occupancyBootstrap) {
                validateOccupancyData(occupancyBootstrap);

                occupancyData = processOccupancyData(occupancyBootstrap);
            }

            function validateOccupancyData(occData) {
                if (!occData || typeof(occData) !== "object") {
                    throw "Occupancy data must be an object";
                }

                var validProps = ["RoomName", "MaxTotalOccupancy", "MaxAdultOccupancy", "MaxChildOccupancy"];

                for (var roomTypeId in occData) {
                    if (!utils.isValidRoomTypeId(roomTypeId)) {
                        throw "`" + roomTypeId + "` is not a valid room type id";
                    }

                    var roomTypeData = occData[roomTypeId];
                    if (!roomTypeData || typeof (roomTypeData) !== "object") {
                        throw "Data for room type `" + roomTypeId + "` of the occupancy data is not an object";
                    }

                    if (Object.getOwnPropertyNames(roomTypeData).length > validProps.length) {
                        throw "Data for room type `" + roomTypeId + "` has too many properties";
                    }

                    if (utils.isUndefNullOrWhitespace(roomTypeData.RoomName)) {
                        throw "Data for room type `" + roomTypeId + "` has an empty or missing room name";
                    }

                    if (roomTypeData.MaxTotalOccupancy && typeof(roomTypeData.MaxTotalOccupancy) !== "number") {
                        throw "Data for room type `" + roomTypeId + "` does not specify a number for MaxTotalOccupancy";
                    }

                    if (roomTypeData.MaxAdultOccupancy && typeof (roomTypeData.MaxAdultOccupancy) !== "number") {
                        throw "Data for room type `" + roomTypeId + "` does not specify a number for MaxAdultOccupancy";
                    }

                    if (roomTypeData.MaxChildOccupancy && typeof (roomTypeData.MaxChildOccupancy) !== "number") {
                        throw "Data for room type `" + roomTypeId + "` does not specify a number for MaxChildOccupancy";
                    }
                }
            }

            function processOccupancyData(occInput) {
                var occData = {};

                for (var roomTypeId in occInput) {
                    var roomTypeData = occInput[roomTypeId];
                    occData[roomTypeId] = Object.freeze({
                        RoomName: roomTypeData.RoomName,
                        MaxTotalOccupancy: roomTypeData.MaxTotalOccupancy || 0,
                        MaxAdultOccupancy: roomTypeData.MaxAdultOccupancy || 0,
                        MaxChildOccupancy: roomTypeData.MaxChildOccupancy || 0
                    });
                }

                return Object.freeze(occData);
            }

            function getOccupancyInfo(roomTypeId) {
                if (!utils.isValidRoomTypeId(roomTypeId)) {
                    throw "Invalid room type id";
                }

                var data = occupancyData[roomTypeId];
                return data ? data : defaultAvailability;
            }

            function getRoomTypeIds() {
                return utils.map(Object.getOwnPropertyNames(occupancyData), function(o) { return parseInt(o); });
            }

            function getRoomTypes() {
                var ids = getRoomTypeIds();
                var roomTypeNames = {};
                for (var i = 0; i < ids.length; i++) {
                    roomTypeNames[ids[i]] = occupancyData[ids[i]].RoomName;
                }

                return roomTypeNames;
            }

            return Object.freeze({
                getRoomTypeIds: getRoomTypeIds,
                getRoomTypes: getRoomTypes,
                getOccupancyInfo: getOccupancyInfo,
                maxInventory: maxInventory
            });

        })(occupancyData.roomTypes, occupancyData.maxInventory);

        return Object.freeze({
            availability: availability,
            occupancy: occupancy,
            attachListener: dataChangeEvent.attachListener
        });
    }
})(window.eviivo.availabilityCalendar.utils, window.eviivo.availabilityCalendar.ui.urlHandler);
"use strict";

window.eviivo = window.eviivo || {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};

window.eviivo.availabilityCalendar.prodData = (function($) {

    var productionValueRegex = /^([0-9]{4})([0-9]{2})([0-9]{2})\/([0-9]+)\/([0-9]+)\/([01]|)\/([01]|)\/([01]|)\/([0-9]*)\/([0-9]*)$/;

    function postProcessData(data) {
        if (!Array.isArray(data)) {
            return data;
        }

        var expandedData = {};
        for (var i = 0; i < data.length; i++) {
            var values = productionValueRegex.exec(data[i]);
            if (values === null) {
                throw "Bad data returned";
            }

            var date = values[1] + "-" + values[2] + "-" + values[3];
            var roomType = values[4];
            var ratePlan = values[5];
            
            expandedData[date] = expandedData[date] || {};
            expandedData[date][roomType] = expandedData[date][roomType] || {};
            expandedData[date][roomType][ratePlan] = $.extend({},
            {
                CanArrive: values[6] === "1" ? true : false,
                CanDepart: values[7] === "1" ? true : false,
                CanContinueStay: values[8] === "1" ? true : false,
                MinLengthOfStay: values[9] !== "" ? parseInt(values[9]) : undefined,
                MaxLengthOfStay: values[10] !== "" ? parseInt(values[10]) : undefined
            });
        }

        return expandedData;
    }

    return Object.freeze({
        postProcessData: postProcessData
    });

})(jQuery);
"use strict";

window.eviivo = window.eviivo || {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};

window.eviivo.availabilityCalendar.ajax = (function($, utils, prodData) {
    
    return function(data, queryUrl, furthestDateAvailable) {

        var furthestDateAvailableFromServer = furthestDateAvailable || null;
        var dataLoadsPending = 0;

        function loadAvailabilityDataBetweenDates(startDate, endDate, successFunc, errorFunc) {
            if (!utils.dateRegex.test(startDate)) {
                throw "Invalid startDate";
            }

            if (!utils.dateRegex.test(endDate)) {
                throw "Invalid endDate";
            }

            if (successFunc && typeof (successFunc) !== 'function') {
                throw 'successFunc must be a function';
            }

            if (errorFunc && typeof (errorFunc) !== 'function') {
                throw 'errorFunc must be a function';
            }

            dataLoadsPending++;

            $.ajax({
                method: 'GET',
                url: queryUrl,
                data: { start: startDate, end: endDate },
                dataType: "json",
                success: function (incomingData, textStatus, jqXHR) {
                    dataLoadsPending--;
                    try {
                        data.availability.insertAvailabilityData(startDate, endDate, prodData.postProcessData(incomingData));
                    } catch (e) {
                        if (errorFunc) {
                            errorFunc('data insertion failed', e);
                        }

                        return;
                    }

                    if (successFunc) {
                        successFunc();
                    }
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    dataLoadsPending--;
                    if (errorFunc) {
                        errorFunc(textStatus, errorThrown);
                    }
                }
            });
        }

        return Object.freeze({
            loadAvailabilityDataBetweenDates: loadAvailabilityDataBetweenDates,
            get furthestDateAvailableFromServer() { return furthestDateAvailableFromServer; },
            get dataLoadPending() { return dataLoadsPending !== 0; }
        });
    };
})(jQuery, window.eviivo.availabilityCalendar.utils, window.eviivo.availabilityCalendar.prodData);
"use strict";

window.eviivo = window.eviivo || {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};

window.eviivo.availabilityCalendar.stateValidation = (function(utils) {

    return function(logic) {

        function validateOccupancies(occupancies) {
            if (occupancies.length <= 0 || occupancies.length > logic.maxInventory) {
                return false;
            }

            for (var i = 0; i < occupancies.length; i++) {
                if (occupancies[i].adult < 1) {
                    return false;
                }

                if (occupancies[i].child < 0) {
                    return false;
                }

                if (!logic.hasCompatibleRoomTypesForOccupancy(occupancies[i])) {
                    return false;
                }
            }

            return true;
        }

        function cleanOccupancies(occupancies) {
            var maxInventory = logic.maximumInventory;
            var cleanedOcc = [];
            for (var i = 0; i < occupancies.length; i++) {
                if (occupancies[i].adults < 1) {
                    continue;
                }

                if (occupancies[i].child < 0) {
                    continue;
                }

                if (!logic.hasCompatibleRoomTypesForOccupancy(occupancies[i])) {
                    continue;
                }

                cleanedOcc.push(utils.immutableDeepClone(occupancies[i]));

                if (cleanedOcc.length === maxInventory) {
                    break;
                }
            }

            return Object.freeze(cleanedOcc);
        }

        function validateRoomTypes(occupancies, roomFilter) {
            if (roomFilter === undefined) {
                roomFilter = logic.roomTypeIds;
            }

            if (roomFilter.length === 0) {
                return false;
            }

            if (!logic.isRoomSelectionAbleToSatisfyOccupancies(roomFilter, occupancies)) {
                return false;
            };

            if (!utils.isSubsetOf(roomFilter, logic.getCompatibleRoomTypesForOccupancies(occupancies))) {
                return false;
            }

            return true;
        }

        function cleanRoomTypes(occupancies, roomFilter) {
            return logic.getCompatibleRoomTypesForOccupancies(occupancies);
        }

        function validateCheckInDate(roomFilter, arrivalDate) {
            try {
                var arrivalStates = logic.getArrivalStates(utils.today(), arrivalDate, roomFilter);
                return arrivalStates[arrivalDate] === "available";
            } catch (e) {
                return false;
            }
        }

        function validateCheckOutDate(roomFilter, arrivalDate, departureDate) {
            try {
                if (!validateCheckInDate(roomFilter, arrivalDate)) {
                    return false;
                }

                var departureStates = logic.getDepartureStates(arrivalDate, departureDate, roomFilter);
                return utils.isOneOf(departureStates[departureDate], "available", "forcedDeparture/maxLos", "forcedDeparture");

            } catch (e) {
                return false;
            }
        }

        return Object.freeze({
            validateOccupancies: validateOccupancies,
            cleanOccupancies: cleanOccupancies,
            validateRoomTypes: validateRoomTypes,
            cleanRoomTypes: cleanRoomTypes,
            validateCheckInDate: validateCheckInDate,
            validateCheckOutDate: validateCheckOutDate
        });
    };
})(window.eviivo.availabilityCalendar.utils);
"use strict";

window.eviivo = window.eviivo || {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};

window.eviivo.availabilityCalendar.logic = (function (utils, validatorFactory) {
    return function (data) {

        if (!data || typeof (data) !== "object") {
            throw "Invalid availabilityData provided";
        }

        var validator = null;

        function cascadeStates(arr, cascadeValues) {
            if (!Array.isArray(cascadeValues)) {
                throw "cascadeValues must be an array";
            }

            if (cascadeValues.length < 1) {
                throw "cascadeValues must have at least one value";
            }

            for (var i = 0; i < cascadeValues.length - 1; i++) {
                if (utils.any(arr, function (value) { return value === cascadeValues[i]; })) {
                    return cascadeValues[i];
                }
            }

            var lastValue = cascadeValues[cascadeValues.length - 1];
            if (utils.all(arr, function (value) { return value === lastValue; })) {
                return lastValue;
            } else {
                throw "There is an unrecognised value in the array that is not in cascade values";
            }
        }

        function getArrivalStates(startDate, endDate, roomTypes) {
            if (!utils.dateRegex.test(startDate)) {
                throw "Invalid startDate provided";
            }

            if (!utils.dateRegex.test(endDate)) {
                throw "Invalid endDate provided";
            }

            if (endDate < startDate) {
                throw "endDate must be after startDate";
            }

            if (roomTypes && !utils.isValidRoomTypeIdArray(roomTypes)) {
                throw "roomTypes must be an array of room type ids";
            }

            if (startDate < data.availability.earliestDateLoaded) {
                throw "Date provided is beyond the loaded data";
            }

            if (endDate > data.availability.furthestDateLoaded) {
                throw "Date provided is beyond the loaded data";
            }

            var dates = {};
            var previousDayState = "firstDay";
            utils.forEachDayBetween(startDate, endDate, function (date) {
                var state = getDateArrivalState(date, roomTypes, previousDayState);
                dates[date] = state;
                previousDayState = state;
            });

            return dates;
        }

        function getDateArrivalState(date, roomTypes, previousDayState) {

            if (previousDayState === "unknown") {
                return "unknown";
            }

            roomTypes = roomTypes || data.availability.getRoomTypes(date);
            var statuses = utils.mapMany(roomTypes, function (roomType) {
                return data.availability.getRatePlans(date, roomType).map(function (ratePlan) {
                    return getRatePlanArrivalState(date, roomType, ratePlan);
                });
            });

            var state = cascadeStates(statuses,
                [
                    "unknown",
                    "available",
                    "cannotArrive",
                    "cannotArrive/minLos",
                    "forcedDeparture",
                    "closed"
                ]);

            if (utils.isOneOf(state, "forcedDeparture", "cannotArrive", "cannotArrive/minLos") && utils.isOneOf(previousDayState, "forcedDeparture", "closed", "afterClosed")) {
                return "afterClosed";
            } else if (state === "forcedDeparture" && previousDayState === "firstDay") {
                return "closed";
            } else {
                return state;
            }
        }

        function hasValidDepartureWindow(arrivalDate, roomType, ratePlan) {
            for (var date = utils.addDays(arrivalDate, 1) ; date < data.availability.furthestDateLoaded; date = utils.addDays(date, 1)) {

                var info = data.availability.getAvailabilityInfo(date, roomType, ratePlan);

                if (info.CanDepart) {
                    return true;
                }

                if (!info.CanContinueStay) {
                    return false;
                }
            }

            return false;
        }

        function getRatePlanArrivalState(date, roomType, ratePlan) {
            var info = data.availability.getAvailabilityInfo(date, roomType, ratePlan);

            if (info.CanArrive && info.MinLengthOfStay <= 1) {
                return hasValidDepartureWindow(date, roomType, ratePlan)
                    ? "available"
                    : "cannotArrive";
            } else if (info.CanArrive && info.MinLengthOfStay > 1) {

                for (var days = 1; days < info.MinLengthOfStay; days++) {
                    if (utils.addDays(date, days) > data.availability.furthestDateLoaded) {
                        return "unknown";
                    }
                    var futureInfo = data.availability.getAvailabilityInfo(utils.addDays(date, days), roomType, ratePlan);
                    if (!futureInfo.CanContinueStay) {
                        return "cannotArrive/minLos";
                    }
                }

                return "available";
            } else if (info.CanDepart && !info.CanArrive && !info.CanContinueStay) {
                return "forcedDeparture";
            } else if (!info.CanDepart && !info.CanArrive && !info.CanContinueStay) {
                return "closed";
            } else {
                return "cannotArrive";
            }
        }

        function getDepartureStates(arrivalDate, calculateUpTo, roomTypes) {
            var dateOptions = getDepartureStatesByDateAndRatePlan(arrivalDate, calculateUpTo, roomTypes);

            return utils.mapValues(dateOptions, function (dateOption) {
                return cascadeStates(utils.map(dateOption, "departureStatus"),
                    [
                        "available",
                        "forcedDeparture",
                        "forcedDeparture/maxLos",
                        "cannotDepart/ctd",
                        "cannotDepart/minLos",
                        "afterClosed",
                        "closed"
                    ]);
            });
        }

        function getDepartureStatesByDateAndRatePlan(arrivalDate, calculateUpTo, roomTypes) {
            if (calculateUpTo <= arrivalDate) {
                throw "calculateUpTo must be after arrivalDate";
            }

            if (arrivalDate > data.availability.furthestDateLoaded || calculateUpTo > data.availability.furthestDateLoaded) {
                throw "arrivalDate or calculateUpTo are beyond the loaded data";
            }

            if (roomTypes && !utils.isValidRoomTypeIdArray(roomTypes)) {
                throw "roomTypes must be an array of room type ids";
            }

            var dates = {};
            utils.forEachDayBetween(utils.addDays(arrivalDate, 1), calculateUpTo, function (date) {
                dates[date] = [];
            });

            roomTypes = roomTypes || data.availability.getRoomTypes(arrivalDate);
            roomTypes.forEach(function (roomType) {
                data.availability.getRatePlans(arrivalDate, roomType).forEach(function (ratePlan) {

                    var departureDatesStatuses = getDepartureStatesForDatesForRatePlan(arrivalDate, roomType, ratePlan, calculateUpTo);

                    for (var date in departureDatesStatuses) {
                        dates[date].push({
                            roomType: roomType,
                            ratePlan: ratePlan,
                            departureStatus: departureDatesStatuses[date]
                        });
                    }
                });
            });

            return dates;
        }

        function getDepartureStatesForDatesForRatePlan(arrivalDate, roomType, ratePlan, calculateUpTo) {
            var initialAvailability = data.availability.getAvailabilityInfo(arrivalDate, roomType, ratePlan);

            var maxLengthOfStay = initialAvailability.MaxLengthOfStay;
            var minLengthOfStay = initialAvailability.MinLengthOfStay;

            var dates = {};
            var lengthOfStay = 1;
            var afterClosed = false;
            utils.forEachDayBetween(utils.addDays(arrivalDate, 1), calculateUpTo, function (date) {

                var info = data.availability.getAvailabilityInfo(date, roomType, ratePlan);

                var status = getRatePlanDepartureState(info, lengthOfStay, afterClosed, minLengthOfStay, maxLengthOfStay);

                if (utils.isOneOf(status, "afterClosed", "closed", "forcedDeparture", "forcedDeparture/maxLos")) {
                    afterClosed = true;
                }

                dates[date] = status;

                lengthOfStay++;
            });

            return dates;
        }

        function getRatePlanDepartureState(info, lengthOfStay, afterClosed, minLengthOfStay, maxLengthOfStay) {
            if (afterClosed || lengthOfStay > maxLengthOfStay) {
                return "afterClosed";
            }
            else if (!info.CanDepart && !info.CanContinueStay) {
                return "closed";
            }
            else if (info.CanDepart && !info.CanContinueStay) {
                return "forcedDeparture";
            }
            else if (lengthOfStay === maxLengthOfStay && info.CanDepart) {
                return "forcedDeparture/maxLos";
            }
            else if (!info.CanDepart) {
                return "cannotDepart/ctd";
            }
            else if (lengthOfStay < minLengthOfStay) {
                return "cannotDepart/minLos";
            }          
            else if (!info.CanArrive && minLengthOfStay === 0) {
                return "closed";
            } else {
                return "available";
            }
        }

        function getMinLengthOfStay(arrivalDate, roomTypes) {
            if (!utils.dateRegex.test(arrivalDate)) {
                throw "Invalid startDate provided";
            }

            if (roomTypes && !utils.isValidRoomTypeIdArray(roomTypes)) {
                throw "roomTypes must be an array of room type ids";
            }

            if (arrivalDate < data.availability.earliestDateLoaded || arrivalDate > data.availability.furthestDateLoaded) {
                throw "Date provided is beyond the loaded data";
            }

            var maxMinLos = 0;
            roomTypes = roomTypes || data.availability.getRoomTypes(arrivalDate);
            roomTypes.forEach(function (roomType) {
                data.availability.getRatePlans(arrivalDate, roomType).forEach(function (ratePlan) {
                    var minLos = data.availability.getAvailabilityInfo(arrivalDate, roomType, ratePlan).MinLengthOfStay;

                    if (minLos > maxMinLos) {
                        maxMinLos = minLos;
                    }
                });
            });

            if (maxMinLos === 0) {
                return 0;
            }

            var lastMinLosDay = utils.addDays(arrivalDate, maxMinLos);
            var departureStates = getDepartureStates(arrivalDate, lastMinLosDay, roomTypes);
            var lengthOfStay = 1;
            utils.forEachDayBetween(utils.addDays(arrivalDate, 1), lastMinLosDay, function (date) {
                if (departureStates[date] !== "cannotDepart/minLos") {
                    return false;
                } else {
                    lengthOfStay++;
                }
            });

            return lengthOfStay;
        }

        function getCompatibleRoomTypesForOccupancies(occupancies) {
            if (!utils.isValidOccupanciesArray(occupancies)) {
                throw "Invalid occupancies list";
            }

            return utils.where(data.occupancy.getRoomTypeIds(), function (roomTypeId) {
                var roomTypeOccupancy = data.occupancy.getOccupancyInfo(roomTypeId);
                return utils.any(occupancies, function (occupancy) {
                    return occupancy.children <= roomTypeOccupancy.MaxChildOccupancy &&
                           occupancy.adults <= roomTypeOccupancy.MaxAdultOccupancy &&
                           occupancy.children + occupancy.adults <= roomTypeOccupancy.MaxTotalOccupancy;
                });
            });
        }

        function hasCompatibleRoomTypesForOccupancy(occupancy) {
            if (!utils.isValidOccupancyValue(occupancy)) {
                throw "Invalid occupancy";
            }

            return utils.any(data.occupancy.getRoomTypeIds(), function (roomTypeId) {
                var roomTypeOccupancy = data.occupancy.getOccupancyInfo(roomTypeId);
                return occupancy.children <= roomTypeOccupancy.MaxChildOccupancy &&
                    occupancy.adults <= roomTypeOccupancy.MaxAdultOccupancy &&
                    occupancy.children + occupancy.adults <= roomTypeOccupancy.MaxTotalOccupancy;
            });
        }

        function getMaximumOccupancy() {
            var occupancies = utils.map(data.occupancy.getRoomTypeIds(), data.occupancy.getOccupancyInfo);

            return Object.freeze({
                adults: utils.max(utils.map(occupancies, function (o) { return o.MaxAdultOccupancy; })),
                children: utils.max(utils.map(occupancies, function (o) { return o.MaxChildOccupancy; })),
                total: utils.max(utils.map(occupancies, function (o) { return o.MaxTotalOccupancy; }))
            });
        }

        function isRoomSelectionAbleToSatisfyOccupancies(roomFilter, occupancies) {
            if (roomFilter === undefined) {
                return true;
            }

            var maxOccupanciesOfRooms = utils.map(roomFilter, data.occupancy.getOccupancyInfo);

            return utils.all(occupancies, function (occupancy) {
                return utils.any(maxOccupanciesOfRooms, function (maxOcc) {
                    return occupancy.adults <= maxOcc.MaxAdultOccupancy &&
                        occupancy.children <= maxOcc.MaxChildOccupancy &&
                        (occupancy.adults + occupancy.children) <= maxOcc.MaxTotalOccupancy;
                });
            });
        }

        return Object.freeze({
            getArrivalStates: getArrivalStates,
            getDepartureStatesByDateAndRatePlan: getDepartureStatesByDateAndRatePlan,
            getDepartureStates: getDepartureStates,
            getMinLengthOfStay: getMinLengthOfStay,
            get furthestDateLoaded() { return data.availability.furthestDateLoaded; },
            get earliestDateLoaded() { return data.availability.earliestDateLoaded; },
            get hasInitialAvailabilityData() { return data.hasInitialAvailabilityData },

            getCompatibleRoomTypesForOccupancies: getCompatibleRoomTypesForOccupancies,
            hasCompatibleRoomTypesForOccupancy: hasCompatibleRoomTypesForOccupancy,
            isRoomSelectionAbleToSatisfyOccupancies: isRoomSelectionAbleToSatisfyOccupancies,
            get maximumInventory() { return data.occupancy.maxInventory; },
            get roomTypes() { return data.occupancy.getRoomTypes(); },
            get roomTypeIds() { return data.occupancy.getRoomTypeIds(); },
            get maximumOccupancy() { return getMaximumOccupancy(); },

            attachListener: data.attachListener,

            get validator() {
                if (validator === null) {
                    validator = validatorFactory(this);
                }

                return validator;
            }
        });
    };
}(window.eviivo.availabilityCalendar.utils, window.eviivo.availabilityCalendar.stateValidation));
"use strict";

window.eviivo = window.eviivo || {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};

window.eviivo.availabilityCalendar.logicCache = (function() {
    var utils = eviivo.availabilityCalendar.utils;

    return function(logic, initialPreloadDaySpan) {

        function arrivalCache(startDate, selectedRoomTypes) {

            var dateCalculatedTo = utils.addDays(startDate, initialPreloadDaySpan);
            dateCalculatedTo = utils.clampAbove(logic.furthestDateLoaded)(dateCalculatedTo);

            var arrivalStates = logic.getArrivalStates(startDate, dateCalculatedTo, selectedRoomTypes);

            return Object.freeze({
                get arrival() { return arrivalStates; },
                get dateCalculatedTo() { return dateCalculatedTo },
                tryEnsureWindow: function (date) {
                    if (date <= dateCalculatedTo) {
                        return;
                    } else if (dateCalculatedTo < logic.furthestDateLoaded) {
                        dateCalculatedTo = logic.furthestDateLoaded;
                        arrivalStates = logic.getArrivalStates(startDate, dateCalculatedTo, selectedRoomTypes);
                    }
                }
            });
        }

        function arrivalDepartureCache(startDate, arrivalDate, selectedRoomTypes) {

            var dateCalculatedTo = utils.addDays(arrivalDate, initialPreloadDaySpan);
            dateCalculatedTo = utils.clampAbove(logic.furthestDateLoaded)(dateCalculatedTo);

            var arrivalStates = logic.getArrivalStates(startDate, dateCalculatedTo, selectedRoomTypes);
            var departureStates = logic.getDepartureStates(arrivalDate, dateCalculatedTo, selectedRoomTypes);

            return Object.freeze({
                get arrival() { return arrivalStates; },
                get departure() { return departureStates; },
                get dateCalculatedTo() { return dateCalculatedTo; },
                minLengthOfStay: logic.getMinLengthOfStay(arrivalDate, selectedRoomTypes),
                tryEnsureWindow: function (date) {
                    if (date <= dateCalculatedTo) {
                        return;
                    } else if (dateCalculatedTo < logic.furthestDateLoaded) {
                        dateCalculatedTo = logic.furthestDateLoaded;
                        arrivalStates = logic.getArrivalStates(startDate, dateCalculatedTo, selectedRoomTypes);
                        departureStates = logic.getDepartureStates(arrivalDate, dateCalculatedTo, selectedRoomTypes);
                    }
                }
                
        });
        }

        return Object.freeze({
            arrivalCache: arrivalCache,
            arrivalDepartureCache: arrivalDepartureCache
        });
    };
})();
"use strict";

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};
window.eviivo.availabilityCalendar.ui = window.eviivo.availabilityCalendar.ui || {};

window.eviivo.availabilityCalendar.ui.tooltipResourceParser = (function(utils) {

    var emptyTooltip = Object.freeze({
        text: undefined,
        forceShow: false,
        pos: "top"
    });

    return function(tooltips) {

        var newTooltips = {};

        for (var name in tooltips) {
            var tooltip = tooltips[name];

            try {
                newTooltips[name] = parseTooltip(tooltip);
            } catch (err) {
                throw "Error parsing tooltip `" + name + "` - " + err;
            }
        }

        return Object.freeze(newTooltips);
    };
    
    function parseTooltip(tooltip) {
        if (utils.isOneOf(tooltip, undefined, null)) {

            return emptyTooltip;

        } else if (typeof (tooltip) === "string") {

            var text = tooltip.trim();
            
            if (text === "") {
                return emptyTooltip;
            }

            return Object.freeze({
                text: text,
                forceShow: false,
                pos: "top"
            });

        } else if (typeof (tooltip) === "object") {

            if (typeof (tooltip.text) !== "string" || tooltip.text.trim() === "") {
                throw "tooltip.text must be set and be a non-empty string";
            }

            if (!utils.isOneOf(tooltip.forceShow, undefined, null, true, false)) {
                throw "tooltip.forceShow must be a boolean if set";
            }

            if (!utils.isOneOf(tooltip.pos, undefined, null, "top", "bottom")) {
                throw "tooltip.pos must be 'top' or 'bottom' if set";
            }

            return Object.freeze({
                text: tooltip.text.trim(),
                forceShow: !!tooltip.forceShow,
                pos: tooltip.pos || "top"
            });

        } else {

            throw "tooltip has an unrecognized value";

        }
    }
})(window.eviivo.availabilityCalendar.utils);
"use strict";

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};
window.eviivo.availabilityCalendar.ui = window.eviivo.availabilityCalendar.ui || {};

window.eviivo.availabilityCalendar.ui.calendarTooltips = (function($, utils, isMobile) {

    var uiClassRegex = /^( *|ui-[a-z0-9-]+)$/i;

    return function($calendarContainer) {

        if (!isMobile)
        {
            $calendarContainer.on('mouseover', 'table.ui-datepicker-calendar td', undefined, function(event) {
                var $target = $(event.target).parents('td');

                if (!$target.hasClass("tooltip-forceShow") && $target.data("tooltip-flash") !== true) {
                    initializeTooltipOnCell($target);
                    $target.tooltip('open');
                }
            });

            $calendarContainer.on('mouseover', 'table.ui-datepicker-calendar td div.ui-tooltip', undefined, function (event) {
                var $target = $(event.target);

                if ($target.hasClass('arrow')) {
                    event.stopPropagation();
                    return false;
                }

                var $cell = $target.parents("td");
                if (!$cell.hasClass('tooltip-forceShow') && $cell.data("tooltip-flash") !== true) {
                    $target.parents("td").tooltip("close");
                }
            });

            $calendarContainer.on('click', 'table.ui-datepicker-calendar td.not-clickable', undefined, function(event) {
                var $cell = $(event.currentTarget);
                var $tooltip = $("div.ui-tooltip", $cell);

                $tooltip.finish();
                var oldColor = $tooltip.css('background-color');
                $tooltip.addClass("tooltip-error");
                var newColor = $tooltip.css("background-color");
                $tooltip.removeClass("tooltip-error");

                $tooltip
                    .css("background-color", newColor)
                    .animate({ "background-color": oldColor },
                    {
                        duration: 2500,
                        easing: "swing",
                        complete: function() {
                            $tooltip.css("background-color", "");
                        }
                    });
            });

        } else { // isMobile

            $calendarContainer.on('click', 'table.ui-datepicker-calendar td.not-clickable', undefined, function (event) {
                var $cell = $(event.currentTarget);
                flashTooltip($cell, $cell.attr('title'), "tooltip-error", 2500);
            });

        }

        function initializeTooltipOnCell($cell, textOverride) {
            if (!$cell.data('ui-tooltip') || textOverride !== undefined) {

                var classAttr = $cell.attr('class') || "";
                var classes = utils.where(classAttr.split(' '), function (c) { return !uiClassRegex.test(c); });

                var pos = $cell.hasClass("tooltip-pos-bottom")
                    ? "bottom"
                    : "top";

                $cell.tooltip({
                    content: function () {
                        var title = textOverride || $(this).attr("title") || "";
                        return utils.splitIntoHtmlLines(title, '|');
                    },
                    items: "[title]",
                    position: {
                        my: "center " + (pos === "top" ? "bottom-15" : "top+15"),
                        at: "center " + (pos === "top" ? "top+10" : "bottom-10"),
                        using: function (position, feedback) {
                            $(this).css(position);
                            $("<div>")
                                .addClass("arrow")
                                .addClass(feedback.vertical)
                                .prependTo(this);
                        }
                    },
                    show: false,
                    hide: false,
                    tooltipClass: classes.join(' '),

                    open: function (event, ui) {
                        var $tooltip = $(ui.tooltip);
                        var $cell = $(this);

                        var tooltipOffset = $tooltip.offset();
                        var cellOffset = $cell.offset();

                        $cell.append($tooltip);

                        $tooltip
                            .css('left', (tooltipOffset.left - cellOffset.left) + "px")
                            .css('top', (tooltipOffset.top - cellOffset.top) + "px");
                    }
                });
            }
        }

        function updateUi() {
            updateStaticTooltips();
        }

        function updateStaticTooltips() {
            $("table.ui-datepicker-calendar td.tooltip-forceShow", $calendarContainer).each(function (_, elem) {
                var $cell = $(elem);

                initializeTooltipOnCell($cell);

                $cell.tooltip('open');
                var tooltips = $cell.data('ui-tooltip').tooltips;
                var tooltipId = Object.getOwnPropertyNames(tooltips)[0];
                var $tooltipElem = $('#' + tooltipId).clone(false, false);
                $cell.tooltip("destroy");

                $tooltipElem.attr("id", null);
                $cell.append($tooltipElem);
            });
        }

        function flashTooltip($cell, text, cssClasses, duration) {
            if (!($cell instanceof $)) {
                throw "$cell must be a jQuery object";
            }

            if ($cell.length === 0) {
                throw "$cell does not represent any elements";
            }

            if ($cell.length > 1) {
                throw "$cell must only represent one element";
            }

            if (!$cell.is("td") || $cell.parents("table.ui-datepicker-calendar").length === 0) {
                throw "$cell is not a cell in the datepicker calendar";
            }

            if (utils.isUndefNullOrWhitespace(text)) {
                throw "text must be defined";
            }

            cssClasses = (function() {
                if (cssClasses === undefined || cssClasses === null) {
                    return [];
                } else if (typeof(cssClasses) === "string") {
                    return utils.cssClassStringToArray(cssClasses);
                } else if (Array.isArray(cssClasses)) {
                    if (!utils.isArrayOf("string")) {
                        throw "cssClasses, if an array, must only contain strings";
                    }

                    return utils.mapMany(cssClasses, utils.cssClassStringToArray);
                } else {
                    throw "cssClasses must be a string or an array of strings";
                }
            })();

            if (duration !== undefined && (typeof (duration) !== "number" || duration <= 0)) {
                throw "duration must be a positive integer";
            }

            duration = duration || 6000;

            $cell.data("tooltip-flash", true);

            initializeTooltipOnCell($cell, text);

            $cell.tooltip('open');
            var tooltips = $cell.data('ui-tooltip').tooltips;
            var tooltipId = Object.getOwnPropertyNames(tooltips)[0];
            var $tooltipElem = $('#' + tooltipId).clone(false, false);
            $cell.tooltip("destroy");

            $tooltipElem.attr("id", null)
                .appendTo($cell)
                .css("display", "none");
            var oldStyles = {
                "background-color": $tooltipElem.css("background-color"),
                "color": $tooltipElem.css("color")
            };
            $tooltipElem.addClass(cssClasses.join(" "));
            var newStyles = {
                "background-color": $tooltipElem.css("background-color"),
                "color": $tooltipElem.css("color")
            };
            $tooltipElem.removeClass(cssClasses.join(" "))
                .css("display", "");

            $tooltipElem.css(newStyles)
                .delay(duration / 6)
                .animate(oldStyles, { duration: (duration * 2.5) / 6, easing: "swing", complete: function () {
                    $tooltipElem.animate({ opacity: 0 }, { duration: (duration * 2.5) / 6, easing: "swing", complete: function() {
                        $tooltipElem.remove();
                        $cell.data("tooltip-flash", false);
                    }});
                }});
        }

        return Object.freeze({
            updateUi: updateUi,
            flashTooltip: flashTooltip
        });
    }
})(window.jQuery, window.eviivo.availabilityCalendar.utils, window.eviivo.utils.isMobile);
"use strict";

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};
window.eviivo.availabilityCalendar.ui = window.eviivo.availabilityCalendar.ui || {};

window.eviivo.availabilityCalendar.ui.calendar = (function($, utils, calendarTooltips, tooltipResourceParser, fastClick, isMobile) {

    var dateClassRegex = /^date-([0-9]{4}-[01][0-9]-[0123][0-9])$/;

    return function (options, logic, ajax, stateMachine) {

        var tooltipResources = processTooltips(options.resources.tooltips);
        
        return function($containerElem, drawState) {

            var updateUiRecursionDepth = 0;
            var hasFirstManualRenderOccured = false;
            var postSelectActionsQueue = [];

            var displayedDate = { month: drawState.month, year: drawState.year };

            var displayedDateRange = {
                datesAreSet: false,
                start: {
                    date: null,
                    month: null,
                    year: null
                },
                end: {
                    date: null,
                    month: null,
                    year: null
                }
            };

            var logicDateRange = {
                datesAreSet: false,
                start: {
                    date: null,
                    month: null,
                    year: null
                },
                end: {
                    date: null,
                    month: null,
                    year: null
                }
            };

            var tooltips = calendarTooltips($containerElem);

            //setup culture and get users current culture
            var culture = "en-gb";
            if (options.resources.selectedCultureLanguageCode != null) {
                culture = options.resources.selectedCultureLanguageCode;
            }

            //Date has been extended via eviivo.webcore.formatting.js, this function takes the current users culture as a parameter and then determines what first day of the week should be, returning it as an offset used with date pickers
            var d = new Date();
            var firstDayOffset = d.evStandardFirstDayCalendarOffset(culture);

            $containerElem.datepicker({
                numberOfMonths: drawState.monthsToShow,
                showButtonPanel: false,
                showOtherMonths: false, // disable visibility of previous month on the current month
                minDate: new Date(), // disable previous dates,
                dateFormat: options.resources.dateDisplayFormat,
                altFormat: options.resources.dateDisplayFormat,
                firstDay: firstDayOffset,

                onChangeMonthYear: function (year, month, inst) {
                    calendarDateChange(month, year);
                },
                afterShow: postCalendarRenderActions,

                beforeShowDay: function (date) {
                    var strDate = utils.toDateString(date);
                    var rawState = stateMachine.dateState(strDate);
                    var dateState = postProcessDateState(date, rawState);

                    //store the calendar displayed date range
                    if (displayedDateRange.datesAreSet == false) {
                        if (dateState.classes != null && dateState.classes.indexOf('arr-past') < 0) {
                            setDisplayDateRange(strDate);
                        }
                    }

                    return [dateState.clickable, dateState.classes.join(" "), dateState.tooltip.text];
                },

                onSelect: function (formattedDate, datePicker) {
                    var dateSelected = new Date(datePicker.selectedYear, datePicker.selectedMonth, datePicker.selectedDay);
                    var dateSelectedStr = utils.toDateString(dateSelected);
                    var result = stateMachine.onDateSelect(dateSelectedStr);
                    var selectActions = parseSelectActions(result);
                    Array.prototype.push.apply(postSelectActionsQueue, selectActions);
                }
            });

            var fastClickObj = null;

            $containerElem.on('mouseover', 'table.ui-datepicker-calendar td', undefined, function(event) {
                var $target = $(event.target).parents('td');

                if (!$target.hasClass('not-clickable')) {
                    if ($target.hasClass('dep-available') || $target.hasClass('dep-forcedDeparture')) {
                        var hoverDate = extractDateFromCell($target);

                        utils.forEachDayBetween(utils.addDays(stateMachine.selectedArrivalDate, 1), hoverDate, function (date) {
                            var hoverClass = date === hoverDate
                                ? "hover-checkout"
                                : "hover-interim";

                            $("td.date-" + date, $containerElem).addClass(hoverClass);
                        });
                    }
                }
            }).on('mouseout', 'table.ui-datepicker-calendar td', undefined, function() {
                $("td", $containerElem).removeClass("hover-interim hover-checkout");
            });

            return Object.freeze({
                updateUi: updateUi,
                refreshData: refreshData,
                get drawState() { return { month: displayedDateRange.start.month, year: displayedDateRange.start.year, monthsToShow: $containerElem.datepicker('option', 'monthsToShow') } },
                destroy: destroy
            });

            function setCalendarMask() {
                $containerElem.parents("div.mod-search-inline").prepend("<div id=\"ac_mask\" style=\"width: 100%;position: absolute;height: 100%;display: inline;z-index: 500;background-color: rgba(120,120,120,0.1);border-radius: 5px;\"></div>");
            }

            function resetCalendarMask() {
                $("#ac_mask").remove();
            }

            //NOTEs: 
            // - the exact end of month also needs to use "max hour" (23:59:59) as that will fix timezone issues or daylight summer saving issues
            // - this date will always be in local timezone, if you need UTC for any reason you will have to 
            //   make use of the getUTCMonth and getUTCFullYear instead in the code above
            // - month in JavaScript is 0-indexed (January is 0, February is 1, etc)
            // - always consider leap years for February in any date calculations
            function getEndOfNextMonth(date) {
                let month = date.getMonth();
                let year = date.getFullYear();

                let nextMonth = month + 1;
                if (nextMonth == 12) {
                    nextMonth = 0;
                    year = year + 1;
                }

                //we default to 30 days and then consider all rest of the cases
                let maxDaysInMonth = 30;

                //all following months have 31 days ALWAYS (Jan, Mar, May, Jul, Aug, Oct, Dec)
                if (
                    nextMonth == 0 ||
                    nextMonth == 2 ||
                    nextMonth == 4 ||
                    nextMonth == 6 ||
                    nextMonth == 7 ||
                    nextMonth == 9 ||
                    nextMonth == 11
                ) {
                    maxDaysInMonth = 31;
                }

                //february should be the special one (28 or 29)
                if (nextMonth == 1) {
                    //leap year has 29 for Feb, all other years have 28
                    maxDaysInMonth = year % 4 == 0 ? 29 : 28;
                }

                //even when you don't need the time and you only need the Date part, 
                //you should still use the "max hour" in your result
                //this will make sure your "end of month" will be exact no matter of timezone and no matter of daylight summer saving times
                return new Date(year, nextMonth, maxDaysInMonth, 23, 59, 59);
            }

            function calendarDateChange(month, year, callback) {

                var selectedDate = utils.toDateString(new Date(year, month - 1, 1));
                var direction = utils.endOfMonth(selectedDate) > utils.toDateString(displayedDateRange.start.date)
                                    ? 1
                                    : selectedDate < utils.toDateString(displayedDateRange.end.date)
                                        ? -1
                                        : 0;

                if (updateUiRecursionDepth === 0 && !ajax.dataLoadPending) {
                    var ajaxStartDate = null;
                    var ajaxEndDate = null;
                    var viewPortStartDate = null;
                    var viewPortEndDate = null;
                    var ajaxCallStatus = "do not call";
                    var strDate = null;
                    if (direction == 1) {
                        incrementDisplayDateRange();

                        strDate = utils.addDays(utils.toDateString(logicDateRange.end.date), 1);
                        ajaxStartDate = utils.startOfMonth(strDate);
                        ajaxEndDate = utils.endOfMonth(utils.addMonths(strDate, options.preloadMonths));

                        let endDate = getEndOfNextMonth(displayedDateRange.end.date);

                        if (ajaxStartDate < ajax.furthestDateAvailableFromServer &&
                            ajaxStartDate < utils.toDateString(endDate))
                        {
                            ajaxEndDate = utils.clampAbove(ajax.furthestDateAvailableFromServer)(ajaxEndDate);
                            ajaxCallStatus = "do the call";
                        }
                        else {
                            // no need to do any ajax call
                            ajaxCallStatus = "do not call (ajaxStartDate < ajax.furthestDateAvailableFromServer && ajaxEndDate > utils.toDateString(logicDateRange.end)) condition not met";
                            ajaxStartDate = null;
                            ajaxEndDate = null;
                        }
                    }
                    else if (direction == -1) {
                        decrementDisplayDateRange();

                        strDate = utils.addDays(utils.toDateString(logicDateRange.start.date), -1);
                        ajaxEndDate = utils.endOfMonth(strDate);
                        ajaxStartDate = utils.startOfMonth(utils.addMonths(ajaxEndDate, -options.preloadMonths));;

                        if (ajaxEndDate > utils.today() &&
                            ajaxEndDate > utils.toDateString(displayedDateRange.start.date))
                        {
                            ajaxStartDate = utils.clampAbove(utils.today())(ajaxStartDate);
                            ajaxCallStatus = "do the call";
                        }
                        else {
                            // no need to do any ajax call
                            ajaxCallStatus = "do not call (ajaxEndDate > utils.today())";
                            ajaxStartDate = null;
                            ajaxEndDate = null;
                        }
                    } else if (direction == 0) {
                        ajaxCallStatus = "do not call (direction == 0)";
                        setDisplayDateRange(selectedDate);
                    }

                    if (ajaxStartDate != null) {
                        callAjax(ajaxStartDate, ajaxEndDate, callback, direction);
                    }
                    
                }
            }

            function callAjax(startDate, endDate, callback, direction) {
                setCalendarMask();

                ajax.loadAvailabilityDataBetweenDates(
                    startDate,
                    endDate,
                    function () {
                        setLogicDateRange(startDate, endDate, direction);

                        updateUi();

                        resetCalendarMask();

                        if (typeof (callback) == "function") {
                            callback();
                        }
                    });
            }

            function refreshData(selectedDate, callback) {
                var dateRangeStart = utils.startOfMonth(selectedDate);
                var dateRangeEnd = utils.endOfMonth(utils.addMonths(dateRangeStart, options.monthsToShow));

                setDisplayDateRange(selectedDate);
                callAjax(dateRangeStart, dateRangeEnd, callback, 0);
            }

            function setDisplayDateRange(strDate) {
                var startDateStartOfMonth = utils.startOfMonth(strDate);
                displayedDateRange.start.date = utils.parseDate(startDateStartOfMonth);
                displayedDateRange.start.month = displayedDateRange.start.date.getMonth();
                displayedDateRange.start.year = displayedDateRange.start.date.getFullYear();
                displayedDateRange.end.date = utils.parseDate(utils.endOfMonth(utils.addMonths(startDateStartOfMonth, options.monthsToShow - 1)));
                displayedDateRange.end.month = displayedDateRange.end.date.getMonth();
                displayedDateRange.end.year = displayedDateRange.end.date.getFullYear();
                displayedDateRange.datesAreSet = true;
            }

            function setLogicDateRange(startDate, endDate, direction) {
                var startDateStartOfMonth = utils.startOfMonth(startDate);
                if (direction <= 0) {
                    logicDateRange.start.date = utils.parseDate(startDateStartOfMonth);
                    logicDateRange.start.month = displayedDateRange.start.date.getMonth();
                    logicDateRange.start.year = displayedDateRange.start.date.getFullYear();
                }
                
                if (direction >= 0) {
                    logicDateRange.end.date = utils.parseDate(utils.endOfMonth(endDate));
                    logicDateRange.end.month = displayedDateRange.end.date.getMonth();
                    logicDateRange.end.year = displayedDateRange.end.date.getFullYear();
                }
                logicDateRange.datesAreSet = true;
            }

            function incrementDisplayDateRange() {
                var startDate = utils.toDateString(displayedDateRange.start.date);
                startDate = utils.addMonths(startDate, 1);
                setDisplayDateRange(startDate);
            }

            function decrementDisplayDateRange() {
                var startDate = utils.toDateString(displayedDateRange.start.date);
                startDate = utils.addMonths(startDate, -1);
                setDisplayDateRange(startDate);
            }

            function updateUi() {
                if (logicDateRange.datesAreSet == false) {
                    setLogicDateRange(logic.earliestDateLoaded, logic.furthestDateLoaded, 0);
                }

                hasFirstManualRenderOccured = true;
                updateUiRecursionDepth++;
                
                $containerElem.datepicker("option",
                {
                    maxDate: utils.parseDate(getMaxDate())
                });
                $containerElem.datepicker("setDate", null);

                setCalendarDates();

                updateUiRecursionDepth--;
                $containerElem.datepicker("refresh");
            }

            function setCalendarDates() {
                var datepickerData = $containerElem.data("datepicker");
                datepickerData.drawMonth = displayedDateRange.start.month;
                datepickerData.drawYear = displayedDateRange.start.year;
                datepickerData.selectedDay = 1;
                datepickerData.selectedMonth = displayedDateRange.start.month;
                datepickerData.selectedYear = displayedDateRange.start.year;
            }

            function getMaxDate() {
                if (logic.furthestDateLoaded === ajax.furthestDateAvailableFromServer) {
                    return utils.addDays(utils.endOfMonth(logic.furthestDateLoaded), 1);
                } else if (logic.furthestDateLoaded === utils.endOfMonth(logic.furthestDateLoaded)) {
                    return utils.addDays(logic.furthestDateLoaded, 1);
                } else {
                    return logic.furthestDateLoaded;
                }
            }

            function postProcessDateState(date, dateState) {
                if (typeof (dateState) !== "object") {
                    throw "Invalid state returned from dateState";
                }

                var clickable = (function() {
                    if (!utils.isOneOf(dateState.clickable, true, false, undefined, null)) {
                        throw "dateState.clickable has an unrecognized value";
                    }

                    return !!dateState.clickable;
                })();

                var cleanedClasses = Object.freeze((function() {
                    if (typeof (dateState.classes) === "string") {
                        return utils.cssClassStringToArray(dateState.classes);
                    } else if (utils.isArrayOf("string")(dateState.classes)) {
                        return utils.mapMany(dateState.classes, utils.cssClassStringToArray);
                    } else {
                        throw "dateState.classes has an unrecognized value";
                    }
                })());

                var tooltip = Object.freeze((function() {
                    if (dateState.tooltipFormat && typeof (dateState.tooltipFormat) !== "object") {
                        throw "dateState.tooltipFormat must be an object to be passed to utils.format";
                    }

                    if (!utils.isOneOf(dateState.forceShowOverride, true, false, undefined, null)) {
                        throw "dateState.forceShowOverride has an unrecognized value";
                    }

                    var tooltipClassName = utils.prefixAll(".", cleanedClasses).join('');
                    if (!tooltipResources[stateMachine.stateName][tooltipClassName]) {
                        console.log("Missing tooltip for " + tooltipClassName);
                    }
                    var tooltip = utils.deepClone(tooltipResources[stateMachine.stateName][tooltipClassName]);

                    if (tooltip != undefined) {
                        tooltip.text = tooltip.text && dateState.tooltipFormat
                            ? utils.format(tooltip.text, dateState.tooltipFormat)
                            : tooltip.text;

                        tooltip.forceShow = utils.isOneOf(dateState.forceShowOverride, true, false)
                            ? dateState.forceShowOverride
                            : tooltip.forceShow;
                    }
                    
                    return tooltip;
                })());

                var classes = Object.freeze((function (classes) {

                    classes.push('tooltip-pos-' + tooltip.pos);
                    classes.push("date-" + utils.toDateString(date));

                    if (tooltip.forceShow) {
                        classes.push('tooltip-forceShow');
                    }

                    if (!dateState.clickable) {
                        classes.push("not-clickable");
                    }

                    return classes;

                })(utils.deepClone(cleanedClasses)));

                return Object.freeze({
                    clickable: clickable,
                    tooltip: tooltip,
                    classes: classes
                });
            }
            
            function parseSelectActions(result) {
                if (result === undefined) {
                    return [];
                }

                if (typeof(result) !== "object") {
                    throw "result of onDateSelect must be undefined or an object";
                }

                var tooltipFlashes = Object.freeze((function() {
                    if (result.flashTooltip === undefined) {
                        return [];
                    }

                    if (Array.isArray(result.flashTooltip)) {
                        return utils.map(result.flashTooltip, parseFlash);
                    } else {
                        return [parseFlash(result.flashTooltip)];
                    }
                    
                    function parseFlash(flash) {
                        if (flash === undefined || flash === null || typeof(flash) !== "object") {
                            throw "flashTooltip element must be an object";
                        }

                        var text = (function() {
                            if (utils.isUndefNullOrWhitespace(flash.text)) {
                                throw "flashTooltip[].text must be a non-empty string";
                            }

                            return flash.text;
                        })();

                        var classes = Object.freeze((function() {
                            if (flash.classes === undefined || flash.classes === null) {
                                return [];
                            }

                            if (typeof (flash.classes) === "string") {
                                return utils.cssClassStringToArray(flash.classes);
                            } else if (Array.isArray(flash.classes)) {
                                return utils.mapMany(flash.classes, utils.cssClassStringToArray);
                            } else {
                                throw "flashTooltip[].classes must be a string or array";
                            }
                        })());

                        var date = (function() {
                            if (!utils.dateRegex.test(flash.date)) {
                                throw "flashTooltip[].date must be a valid date string";
                            }

                            return flash.date;
                        })();

                        return Object.freeze({
                            action: "flashTooltip",
                            text: text,
                            classes: classes,
                            date: date
                        });
                    }
                })());

                return tooltipFlashes;
            }

            function processSelectAction(action) {
                if (action.action === "flashTooltip") {
                    tooltips.flashTooltip($("td.date-" + action.date, $containerElem), action.text, action.classes);
                }
            }

            function postCalendarRenderActions() {
                if (updateUiRecursionDepth !== 0 || !hasFirstManualRenderOccured) {
                    return;
                }

                tooltips.updateUi();

                $("td a.ui-state-highlight").removeClass("ui-state-highlight");
                $("td a.ui-state-active").removeClass("ui-state-active");
                
                var lastShownMonth = getLastShownMonth();
                var calendarMaxDate = utils.toDateString($containerElem.datepicker('option', 'maxDate'));
                if (lastShownMonth.lastDate > calendarMaxDate) {
                    if (lastShownMonth.lastDate > ajax.furthestDateAvailableFromServer) {

                        var message = utils.splitIntoHtmlLines(utils.format(options.resources.limitOfData,
                        {
                            PhoneNumber: options.property.TelephoneNumber
                        }), '|');

                        var $wrapper = $('<div class="calendar-overlay calendar-overlay-limit-of-data"><div class="calendar-overlay-text"><p></p></div></div>');
                        $wrapper.find("div.calendar-overlay-text").find("p").html(message);

                        lastShownMonth.$tableElem.after($wrapper);
                        $wrapper.append(lastShownMonth.$tableElem);

                    } else if (lastShownMonth.lastDate > logic.furthestDateLoaded) {

                        var message = utils.splitIntoHtmlLines(utils.format(options.resources.dataLoading,
                        {
                    
                        }));

                        var $wrapper = $('<div class="calendar-overlay calendar-overlay-data-loading"><div class="calendar-overlay-text"><p></p></div></div>');
                        $wrapper.find("div.calendar-overlay-text").find("p").html(message);

                        lastShownMonth.$tableElem.after($wrapper);
                        $wrapper.append(lastShownMonth.$tableElem);
                    }
                }
                if (postSelectActionsQueue.length > 0) {
                    postSelectActionsQueue.forEach(processSelectAction);
                    postSelectActionsQueue = [];
                }
            }

            function getLastShownMonth() {
                var monthStr
                if (drawState.monthsToShow === 1) {
                    monthStr = utils.toDateString(displayedDateRange.end.date);
                    return {
                        month: displayedDateRange.end.month + 1,
                        year: displayedDateRange.end.year,
                        lastDate: utils.endOfMonth(monthStr),
                        $tableElem: $("table.ui-datepicker-calendar", $containerElem)
                    };
                } else {
                    monthStr = utils.toDateString(displayedDateRange.end.date);
                    return {
                        month: displayedDateRange.end.month + 1,
                        year: displayedDateRange.end.year,
                        lastDate: utils.endOfMonth(monthStr),
                        $tableElem: $(".ui-datepicker-group-last table.ui-datepicker-calendar", $containerElem)
                    };
                }
            }

            function extractDateFromCell($cell) {
                if (!($cell instanceof $)) {
                    throw "$cell must be a jQuery object";
                }

                if ($cell.length !== 1) {
                    throw "$cell must refer to one element";
                }

                

                var classes = utils.cssClassStringToArray($cell.prop('class'));
                var dateClass = utils.single(classes, function(cls) { return dateClassRegex.test(cls); });
                return dateClassRegex.exec(dateClass)[1];
            }

            function destroy() {
                $containerElem.datepicker('destroy');
                $containerElem.empty().removeClass('calendar-displayed');
                if (fastClickObj) {
                    fastClickObj.destroy();
                }
            }
        }

        function processTooltips(tooltipStates) {

            if (!utils.setEqual(stateMachine.stateNames, Object.getOwnPropertyNames(tooltipStates))) {
                throw "Tooltip resources state level does not match states in state machine";
            }

            var processedTooltips = {};

            for (var stateName in tooltipStates) {
                processedTooltips[stateName] = tooltipResourceParser(tooltipStates[stateName]);
            }

            return Object.freeze(processedTooltips);
        }
    }

})(jQuery, window.eviivo.availabilityCalendar.utils, window.eviivo.availabilityCalendar.ui.calendarTooltips, window.eviivo.availabilityCalendar.ui.tooltipResourceParser, window.FastClick, window.eviivo.utils.isMobile);
"use strict";

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};
window.eviivo.availabilityCalendar.ui = window.eviivo.availabilityCalendar.ui || {};

/* Implementation note: matchMedia.addListener was considered for this code,
    and while it is more performant, it is still experimental (as per MDN on 06/12/16)
    and is not supported by IE9 anyway. This would have lead to a seperate implemenation
    of the switching logic just for IE9. Instead, all browsers will use the resize+test
    implementation, with IE9 using a polyfill for matchMedia, which is not considered
    experimental.

    matchMedia is used over directly using screen.width or $(window).width() or innerWidth
    or any of those as they are generally inconsistent across browsers, and matchMedia allows
    the code to exactly mimic the breakpoints that the CSS based media queries will use */

window.eviivo.availabilityCalendar.ui.responsiveCalendar = (function($, utils, calendar) {

    return function($calendarDisplayElems, options, stateMachine, logic, ajax) {

        var calendarFactory = calendar(options, logic, ajax, stateMachine);

        var ranges = getCalendarDisplayRanges($calendarDisplayElems);

        var currentCalendarRange = null;
        var currentCalendar = null;

        function getCurrentlyApplicableCalendarRange() {

            var applicableElem = null;
            for (var i = 0; i < ranges.length; i++) {
                var range = ranges[i];
                if (range.isCurrentlyApplicable) {
                    if (applicableElem !== null) {
                        throw "Algorithm error: More than one applicable element";
                    }
                    
                    applicableElem = range;
                }
            }

            if (applicableElem === null) {
                throw "Algorithm error: No elem is applicable at the moment";
            }

            return applicableElem;
        }

        function refreshData(selectedDate) {
            currentCalendar.refreshData(selectedDate == null ? stateMachine.selectedArrivalDate : selectedDate, updateUi);
        }

        function updateUi() {
            var applicableRange = getCurrentlyApplicableCalendarRange();

            if (applicableRange !== currentCalendarRange) {
                switchCalendarDisplay(applicableRange);
            }

            currentCalendar.updateUi();
        }

        function switchCalendarDisplay(newRange) {
            console.log("Switching to :" + utils.$elemSummary(newRange.$elem));

            var drawState;

            if (stateMachine.selectedArrivalDate !== null) {
                var date = utils.parseDate(stateMachine.selectedArrivalDate);
                drawState = { month: date.getMonth() + 1, year: date.getFullYear() };
            } else if (currentCalendar !== null) {
                drawState = currentCalendar.drawState;
            } else {
                var date = new Date();
                drawState = { month: date.getMonth() + 1, year: date.getFullYear() };
            }

            if (currentCalendar !== null) {
                currentCalendar.destroy();
            }

            currentCalendar = null;
            currentCalendarRange = null;

            currentCalendarRange = newRange;
            drawState.monthsToShow = currentCalendarRange.monthsToShow;

            currentCalendar = calendarFactory(currentCalendarRange.$elem, drawState);
            currentCalendarRange.$elem.addClass("calendar-displayed");
        }

        return {
            updateUi: updateUi,
            refreshData: refreshData
        };
    };

    function getCalendarDisplayRanges($calendarElems) {

        if ($calendarElems.is(function(_, elem) { return $(elem).data('months-to-show') === undefined; })) {
            throw "One or more calendar container elems does not have a data-months-to-show attribute";
        }

        // There should be exactly one elem that is either missing data-min-width or have data-min-width = 0
        var minWidthMissing = utils.$hasCount($calendarElems,
            1,
            function(elem) {
                var $elem = $(elem);
                return $elem.data("min-width") === undefined || $elem.data('min-width') === 0;
            });

        if (!minWidthMissing) {
            throw "There is either no, or more than one, calendar container elem that has either not specified min-width or has a min-width of 0. There must be one and only one.";
        }

        // There should also be exactly one elem missing data-max-width (representing the largest size and therefore the 'max' being infinity)
        var maxWidthMissing = utils.$hasCount($calendarElems,
            1,
            function(elem) {
                var $elem = $(elem);
                return $elem.data("max-width") === undefined;
            });

        if (!maxWidthMissing) {
            throw "There is either no, or more than one, calendar container elem that has not specified max-width. There must be one and only one.";
        }

        var ranges = [];
        $calendarElems.each(function(_, elem) {
            var $elem = $(elem);
            var minWidth = parseInt($elem.data('min-width') || 0);
            var maxWidth = parseInt($elem.data('max-width') || utils.MAX_SAFE_INTEGER);

            for (var i = 0; i < ranges.length; i++) {
                if (minWidth >= ranges[i].min && minWidth <= ranges[i].max) {
                    throw "Element `" +
                        utils.$elemSummary($elem) +
                        "`'s min-width conflicts with element `" +
                        utils.$elemSummary(ranges[i].$elem) +
                        "`'s range";
                } else if (maxWidth >= ranges[i].min && maxWidth <= ranges[i].max) {
                    throw "Element `" +
                        utils.$elemSummary($elem) +
                        "`'s max-width conflicts with element `" +
                        utils.$elemSummary(ranges[i].$elem) +
                        "`'s range";
                }
            }

            var mediaQuery = (function() {
                if (minWidth !== 0 && maxWidth !== utils.MAX_SAFE_INTEGER) {
                    return "(min-width: " + minWidth + "px) and (max-width: " + maxWidth + "px)";
                } else if (minWidth !== 0) {
                    return "(min-width: " + minWidth + "px)";
                } else if (maxWidth !== utils.MAX_SAFE_INTEGER) {
                    return "(max-width: " + maxWidth + "px)";
                } else {
                    throw "Algorithm error";
                }
            })();
            
            ranges.push(Object.freeze({
                min: minWidth,
                max: maxWidth,
                monthsToShow: $elem.data('months-to-show'),
                $elem: $elem,
                get isCurrentlyApplicable() { return Modernizr.mq(mediaQuery); }
            }));
        });

        ranges.sort(function (r1, r2) { return r1.min - r2.min; });
        
        // Final check that all possible widths are covered by the ranges
        if (ranges[0].min !== 0) {
            throw "Algorithm error: First range does not have a min-width of 0";
        }

        if (ranges[ranges.length - 1].max !== utils.MAX_SAFE_INTEGER) {
            throw "Algorithm error: Last range does not have a max-width of MAX_SAFE_INTEGER";
        }

        for (var i = 1; i < ranges.length; i++) {
            var range = ranges[i];
            if (range.min !== ranges[i - 1].max + 1) {
                var a = range.min < ranges[i - 1].max
                    ? range.min
                    : ranges[i - 1].max;
                var b = range.min < ranges[i - 1].max
                    ? ranges[i - 1].max
                    : range.min;

                throw "There is gap in the calendar width ranges - all possible window widths must be accounted for. Gap is between " + a + " and " + b;
            }
        }

        return Object.freeze(ranges);
    }
})(jQuery, window.eviivo.availabilityCalendar.utils, window.eviivo.availabilityCalendar.ui.calendar);
"use strict";

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};
window.eviivo.availabilityCalendar.ui = window.eviivo.availabilityCalendar.ui || {};
window.eviivo.availabilityCalendar.ui.states = window.eviivo.availabilityCalendar.ui.states || {};


window.eviivo.availabilityCalendar.ui.states.arrival = (function(utils) {
    
    return function(options, logic) {
        
        return Object.freeze({
            dateState: function(state, logicCache) {

                var states = logicCache.arrivalCache(utils.today(), state.selectedRoomTypes);

                return function(state, date) {
                    states.tryEnsureWindow(date);

                    var arrivalState = states.arrival[date];

                    if (date < utils.today()) {
                        return {
                            classes: "arr-past",
                            clickable: false
                        };
                    } else if (date > states.dateCalculatedTo) {
                        return {
                            classes: "arr-unknown",
                            clickable: false
                        };
                    } else if (arrivalState) {
                        return {
                            classes: utils.prefixAll("arr-", arrivalState.split("/")),
                            clickable: arrivalState === "available"
                        };
                    } else {
                        throw "UNKNOWN DATE STATE";
                    }
                }
            },

            onDateSelect: function(stateMachine, date) {
                stateMachine.transitionState("departure", date, stateMachine.selectedOccupancies, stateMachine.selectedRoomTypes);
            },

            allowShowPrices: false,
            allowClearDates: false,

            transitionFunc: function(selectedOccupancies, selectedRoomTypes) {
                this.state.selectedArrivalDate = null;
                this.state.selectedDepartureDate = null;
                this.state.selectedOccupancies = selectedOccupancies;
                this.state.selectedRoomTypes = selectedRoomTypes;
            }
        });
    }
})(window.eviivo.availabilityCalendar.utils);
"use strict";

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};
window.eviivo.availabilityCalendar.ui = window.eviivo.availabilityCalendar.ui || {};
window.eviivo.availabilityCalendar.ui.states = window.eviivo.availabilityCalendar.ui.states || {};

window.eviivo.availabilityCalendar.ui.states.departure = (function(utils) {

    function classesFromState(prefix, state) { return utils.prefixAll(prefix + "-", state.split('/')); }

    function firstDayWithConditionAfter(states, startDate, predicate) {
        for (var date = utils.addDays(startDate, 1); states[date] !== undefined; date = utils.addDays(date, 1)) {
            if (predicate(states[date])) {
                return date;
            }
        }

        throw new "No date with condition";
    }

    return function (options, logic) {
        
        return Object.freeze({
            dateState: function(state, logicCache) {

                var states = logicCache.arrivalDepartureCache(utils.today(), state.selectedArrivalDate, state.selectedRoomTypes);

                var firstAvailableCheckOutDay = firstDayWithConditionAfter(states.departure, state.selectedArrivalDate, function(state) { return utils.isOneOf(state, "available", "forcedDeparture/maxLos", "forcedDeparture"); });
                var actualMinimumLengthOfStay = utils.nightsBetween(state.selectedArrivalDate, firstAvailableCheckOutDay);

                return function(state, date) {
                    states.tryEnsureWindow(date);

                    var departureState = states.departure[date];

                    if (date < utils.today()) {
                        return {
                            classes: "dep-past",
                            clickable: false
                        };
                    } else if (date > states.dateCalculatedTo) {
                        return {
                            classes: ["dep-unknown"],
                            clickable: false
                        };
                    } else if (date < state.selectedArrivalDate) {
                        return {
                            classes: ["dep-beforeCheckInDate"].concat(classesFromState("arr", states.arrival[date])),
                            clickable: states.arrival[date] === "available"
                        };
                    } else if (date === state.selectedArrivalDate) {
                        return {
                            classes: "dep-checkInDate",
                            clickable: true,
                            tooltipFormat: { MinLengthOfStay: actualMinimumLengthOfStay },
                            forceShowOverride: actualMinimumLengthOfStay > 1
                        };
                    } else if (departureState === "cannotDepart/minLos") {

                        var firstPotentialCheckOutDay = firstDayWithConditionAfter(states.departure, date, function (state) { return state !== "cannotDepart/minLos"; });
                        
                        return {
                            classes: ["dep-cannotDepart", "dep-minLos"],
                            clickable: utils.isOneOf(states.departure[firstPotentialCheckOutDay], "available", "forcedDeparture/maxLos", "forcedDeparture"),
                            tooltipFormat: { MinLengthOfStay: states.minLengthOfStay }
                        };
                    } else if (departureState === "afterClosed") {
                        var arrivalState = states.arrival[date];
                        return {
                            classes: ["dep-afterClosed"].concat(utils.prefixAll("arr-", arrivalState.split('/'))),
                            clickable: arrivalState === "available"
                        };
                    } else if (departureState) {
                        return {
                            classes: utils.prefixAll("dep-", departureState.split('/')),
                            clickable: utils.isOneOf(departureState, "available", "forcedDeparture/maxLos", "forcedDeparture")
                        };
                    } else {
                        throw "UNKNOWN DATE STATE";
                    }
                }
            },

            onDateSelect: function(stateMachine, date) {

                if (date <= stateMachine.selectedArrivalDate) {
                    stateMachine.transitionState("departure", date, stateMachine.selectedOccupancies, stateMachine.selectedRoomTypes);
                    return;
                }

                var minLos = logic.getMinLengthOfStay(stateMachine.selectedArrivalDate, stateMachine.selectedRoomTypes);
                var firstAvailableCheckout = utils.addDays(stateMachine.selectedArrivalDate, minLos);

                if (date <= firstAvailableCheckout) {
                    stateMachine.transitionState("selected", stateMachine.selectedArrivalDate, firstAvailableCheckout, stateMachine.selectedOccupancies, stateMachine.selectedRoomTypes);
                    if (date < firstAvailableCheckout) {
                        return {
                            flashTooltip: {
                                date: firstAvailableCheckout,
                                text: utils.format(options.resources.forcedMinLos, { LengthOfStay: minLos }),
                                classes: "tooltip-error"
                            }
                        };
                    } else {
                        return;
                    }
                }

                var departureState = logic.getDepartureStates(stateMachine.selectedArrivalDate, date, stateMachine.selectedRoomTypes)[date];
                if (departureState === "afterClosed") {

                    stateMachine.transitionState("departure", date, stateMachine.selectedOccupancies, stateMachine.selectedRoomTypes);

                } else {

                    stateMachine.transitionState("selected", stateMachine.selectedArrivalDate, date, stateMachine.selectedOccupancies, stateMachine.selectedRoomTypes);

                }
            },

            allowShowPrices: false,
            allowClearDates: true,

            transitionFunc: function(selectedArrivalDate, selectedOccupancies, selectedRoomTypes) {
                this.state.selectedArrivalDate = selectedArrivalDate;
                this.state.selectedDepartureDate = null;
                this.state.selectedOccupancies = selectedOccupancies;
                this.state.selectedRoomTypes = selectedRoomTypes;
            }
        });
    }
})(window.eviivo.availabilityCalendar.utils);
"use strict";

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};
window.eviivo.availabilityCalendar.ui = window.eviivo.availabilityCalendar.ui || {};
window.eviivo.availabilityCalendar.ui.states = window.eviivo.availabilityCalendar.ui.states || {};

window.eviivo.availabilityCalendar.ui.states.selected = (function(utils) {

    function classesFromState(prefix, state) { return utils.prefixAll(prefix + "-", state.split('/')); }

    return function(options, logic) {

        return Object.freeze({
            dateState: function(state, logicCache) {

                var states = logicCache.arrivalDepartureCache(utils.today(), state.selectedArrivalDate, state.selectedRoomTypes);

                var lengthOfStay = utils.nightsBetween(state.selectedArrivalDate, state.selectedDepartureDate);
                
                return function(state, date) {
                    states.tryEnsureWindow(date);

                    var arrivalState = states.arrival[date];
                    var departureState = states.departure[date];
                    
                    var tooltipFormat = {
                        LengthOfStay: lengthOfStay,
                        MinLengthOfStay: states.minLengthOfStay
                    };

                    if (date < utils.today()) {
                        return {
                            classes: ["selected-past"],
                            clickable: false,
                            tooltipFormat: tooltipFormat
                        };
                    } else if (date > states.dateCalculatedTo) {
                        return {
                            classes: ["selected-unknown"],
                            clickable: false,
                            tooltipFormat: tooltipFormat
                        }
                    } else if (date < state.selectedArrivalDate) {
                        return {
                            classes: ["selected-beforeCheckInDate"].concat(classesFromState("arr", arrivalState)),
                            clickable: arrivalState === "available",
                            tooltipFormat: tooltipFormat
                        };
                    } else if (date === state.selectedArrivalDate) {
                        return {
                            classes: lengthOfStay > 1 ? "selected-checkInDate" : ["selected-checkInDate", "selected-lastNight"],
                            clickable: true,
                            tooltipFormat: tooltipFormat
                        };
                    } else if (state.selectedArrivalDate < date && date < state.selectedDepartureDate) {
                        var lastNightClass = utils.addDays(date, 1) === state.selectedDepartureDate
                            ? ["selected-lastNight"]
                            : [];
                        return {
                            classes: ["selected-interim"].concat(lastNightClass),
                            clickable: utils.isOneOf(departureState, "available", "forcedDeparture/maxLos", "forcedDeparture", "cannotDepart/minLos"),
                            tooltipFormat: tooltipFormat
                        };
                    } else if (date === state.selectedDepartureDate) {
                        return {
                            classes: ["selected-checkOutDate"].concat(classesFromState("dep", departureState)),
                            clickable: utils.isOneOf(departureState, "available", "forcedDeparture/maxLos", "forcedDeparture"),
                            tooltipFormat: tooltipFormat
                        };
                    } else if (date > state.selectedDepartureDate && departureState !== "afterClosed") {
                        return {
                            classes: ["selected-afterCheckOutDate"].concat(classesFromState("dep", departureState)),
                            clickable: utils.isOneOf(departureState, "available", "forcedDeparture/maxLos", "forcedDeparture"),
                            tooltipFormat: tooltipFormat
                        }
                    } else if (date > state.selectedDepartureDate && departureState === "afterClosed") {
                        return {
                            classes: ["selected-afterCheckOutDate", "dep-afterClosed"].concat(classesFromState("arr", arrivalState)),
                            clickable: arrivalState === "available",
                            tooltipFormat: tooltipFormat
                        }
                    } else {
                        throw "INVALID DATE STATE";
                    }
                }
            },

            onDateSelect: function(stateMachine, date) {
                if (date <= stateMachine.selectedArrivalDate) {

                    stateMachine.transitionState("departure", date, stateMachine.selectedOccupancies, stateMachine.selectedRoomTypes);

                } else if (date < stateMachine.selectedDepartureDate) {

                    var minLos = logic.getMinLengthOfStay(stateMachine.selectedArrivalDate, stateMachine.selectedRoomTypes);
                    var firstAvailableCheckout = utils.addDays(stateMachine.selectedArrivalDate, minLos);
                    
                    stateMachine.transitionState("selected", stateMachine.selectedArrivalDate, utils.clampBelow(firstAvailableCheckout)(date), stateMachine.selectedOccupancies, stateMachine.selectedRoomTypes);

                } else {

                    var departureState = logic.getDepartureStates(stateMachine.selectedArrivalDate, date, stateMachine.selectedRoomTypes)[date];
                    if (departureState === "afterClosed") {

                        stateMachine.transitionState("departure", date, stateMachine.selectedOccupancies, stateMachine.selectedRoomTypes);

                    } else {

                        stateMachine.transitionState("selected", stateMachine.selectedArrivalDate, date, stateMachine.selectedOccupancies, stateMachine.selectedRoomTypes);

                    }
                }
            },

            allowShowPrices: true,
            allowClearDates: true,

            transitionFunc: function(selectedArrivalDate, selectedDepartureDate, selectedOccupancies, selectedRoomTypes) {
                this.state.selectedArrivalDate = selectedArrivalDate;
                this.state.selectedDepartureDate = selectedDepartureDate;
                this.state.selectedOccupancies = selectedOccupancies;
                this.state.selectedRoomTypes = selectedRoomTypes;
            }

        });
    }
})(window.eviivo.availabilityCalendar.utils);
"use strict";

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};
window.eviivo.availabilityCalendar.ui = window.eviivo.availabilityCalendar.ui || {};
window.eviivo.availabilityCalendar.ui.states = window.eviivo.availabilityCalendar.ui.states || {};

window.eviivo.availabilityCalendar.ui.states.searchDone = (function (selectedState) {
    return selectedState;
})(window.eviivo.availabilityCalendar.ui.states.selected);
"use strict";

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};
window.eviivo.availabilityCalendar.ui = window.eviivo.availabilityCalendar.ui || {};

window.eviivo.availabilityCalendar.ui.stateMachine = (function() {
    var utils = eviivo.availabilityCalendar.utils;

    var stateFuncs = utils.immutableDeepClone(eviivo.availabilityCalendar.ui.states);

    var stateNames = [];
    for (var name in stateFuncs) {
        stateNames.push(name);
    }

    var stateRequisites = Object.freeze({
        dateState: "function",
        onDateSelect: "function",
        allowShowPrices: "boolean",
        allowClearDates: "boolean",
        transitionFunc: "function"
    });

    function checkStates(states) {
        for (var state in states) {
            if (!states.hasOwnProperty(state)) { continue; }
            for (var prop in states[state]) {
                if (!states[state].hasOwnProperty(prop)) { continue; }
                if (!stateRequisites[prop] || typeof(states[state][prop]) !== stateRequisites[prop]) {
                    throw "Invalid state definition in availability calendar state machine";
                }
            }
        }

        for (var prop in stateRequisites) {
            if (!stateRequisites.hasOwnProperty(prop)) { continue; }
            for (var state in states) {
                if (!states.hasOwnProperty(state)) { continue; }
                if (states[state][prop] === undefined || typeof (states[state][prop]) !== stateRequisites[prop]) {
                    throw "Invalid state definition in availability calendar state machine";
                }
            }
        }
    }
    
    return function(options, logic, initialState) {

        var states = {};
        for (var stateName in stateFuncs) {
            states[stateName] = stateFuncs[stateName](options, logic);
        }

        checkStates(states);

        var currentStateName = null;
        var currentState = (function() {

            var selectedArrivalDate = null;
            var selectedDepartureDate = null;
            var selectedOccupancies = null;
            var selectedRoomTypes = undefined;

            return Object.freeze({
                get selectedArrivalDate() { return selectedArrivalDate; },
                set selectedArrivalDate(date) {
                    if (date === null || utils.dateRegex.test(date) === true) {
                        selectedArrivalDate = date;
                    } else {
                        throw "Invalid date format";
                    }
                },
                get selectedDepartureDate() { return selectedDepartureDate; },
                set selectedDepartureDate(date) {
                    if (date === null || utils.dateRegex.test(date) === true) {
                        selectedDepartureDate = date;
                    } else {
                        throw "Invalid date format";
                    }
                },
                get selectedOccupancies() { return selectedOccupancies; },
                set selectedOccupancies(occupancies) {
                    if (!utils.isValidOccupanciesArray(occupancies)) {
                        throw "Invalid occupancies array";
                    }

                    selectedOccupancies = Object.freeze(utils.deepClone(occupancies));
                },

                get selectedRoomTypes() { return selectedRoomTypes; },
                set selectedRoomTypes(roomTypes) {
                    if (roomTypes === undefined) {
                        selectedRoomTypes = undefined;
                    } else if (utils.isValidRoomTypeIdArray(roomTypes)) {
                        selectedRoomTypes = Object.freeze(utils.deepClone(roomTypes));
                    } else {
                        throw "Invalid room type array";
                    }
                }
            });
        })();

        var stateChangeRecursionDepth = 0;
        var stateChangeListeners = [];

        var onDateSelectFuncCache = null;
        var dateStateFuncCache = null;

        function transitionState(newState) {

            if (!newState ||
                !states[newState] ||
                !states[newState].transitionFunc ||
                typeof(states[newState].transitionFunc) !== "function") {
                throw "Invalid state to transition to";
            }

            var transitionFunc = states[newState].transitionFunc;

            if (transitionFunc.length !== arguments.length - 1) {
                throw "Incorrect number of arguments for transition function";
            }

            // Not using slice or similar to avoid optimizers bailing out
            var transitionArgs = new Array(arguments.length - 1);
            for (var i = 1; i < arguments.length; i++) {
                transitionArgs[i - 1] = arguments[i];
            }

            var oldStateName = currentStateName;
            var oldState = utils.immutableDeepClone(currentState);
            var success = false;
            try {
                stateChangeRecursionDepth++;
                currentStateName = newState;
                onDateSelectFuncCache = null;
                dateStateFuncCache = null;
                transitionFunc.apply({ state: currentState, stateMachine: this }, transitionArgs);

                if (this.selectedOccupancies === undefined ||!logic.validator.validateOccupancies(this.selectedOccupancies)) {
                    throw "Invalid occupancies set by state transition function";
                } else if (this.selectedRoomTypes !== undefined && !logic.validator.validateRoomTypes(this.selectedOccupancies, this.selectedRoomTypes)) {
                    throw "Invalid set of room types set by state transition function";
                } else if (this.selectedArrivalDate === null && this.selectedDepartureDate !== null) {
                    throw "Departure date set with no arrival date by state transition function";
                }
                //else if (this.selectedRoomTypes !== undefined && (this.selectedDepartureDate == null || this.selectedArrivalDate === null)) {
                //    throw "Invalid dates set by state transition function";
                //} 

                success = true;
            } catch (ex) {
                currentStateName = oldStateName;
                for (var statePart in oldState) {
                    currentState[statePart] = oldState[statePart];
                }
                console.error("Error during state transition: " + ex);
            } finally {
                stateChangeRecursionDepth--;
            }

            if (success && stateChangeRecursionDepth === 0) {
                for (var i = 0; i < stateChangeListeners.length; i++) {
                    stateChangeListeners[i]();
                }
            }
        }
        
        var currentStateWrapper = utils.readOnlyWrapper(currentState);
        

        var stateMachineProps = {
            get state() { return currentStateWrapper; },
            get stateName() { return currentStateName; },
            get stateNames() { return utils.deepClone(stateNames); },

            get allowShowPrices() { return states[currentStateName].allowShowPrices; },
            get allowClearDates() { return states[currentStateName].allowClearDates; },
            get onDateSelect() {
                if (!onDateSelectFuncCache) {
                    onDateSelectFuncCache = states[currentStateName].onDateSelect.bind(null, this);
                }

                return onDateSelectFuncCache;
            },
            get dateState() {
                if (!dateStateFuncCache) {
                    var logicCache = window.eviivo.availabilityCalendar.logicCache(logic, 100);
                    var dateStateFunc = states[currentStateName].dateState(currentStateWrapper, logicCache);
                    dateStateFuncCache = function(date) { return dateStateFunc(currentStateWrapper, date); };
                }

                return dateStateFuncCache;
            },

            transitionState: transitionState,

            forceRefresh: function() {
                onDateSelectFuncCache = null;
                dateStateFuncCache = null;
            },

            attachListener: function(func) {
                if (typeof(func) !== "function") {
                    throw "func must be a function";
                }

                stateChangeListeners.push(func);
            }
        };

        var stateMachine = Object.create(currentStateWrapper, utils.getOwnPropertyDescriptors(stateMachineProps));
        
        var newArgs = [];
        for (var i = 2; i < arguments.length; i++) {
            newArgs[i - 2] = arguments[i];
        }

        transitionState.apply(stateMachine, newArgs);

        return Object.freeze(stateMachine);
    }
})();
"use strict";

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};
window.eviivo.availabilityCalendar.ui = window.eviivo.availabilityCalendar.ui || {};

window.eviivo.availabilityCalendar.ui.occupancySelector = (function($, utils, dropdownFactory) {

    return function ($occupancySelector, stateMachine, logic, resources, occupancySelectorOptions) {

        var $occupancyBox = $("span.cp-inputValues", $occupancySelector);
        var $tooltip = $("div.cp-tooltip", $occupancySelector)
            .on("selectmenuchange", updateSelection)
            .on('customDropdown.hide', closeTooltip)
            .on('customDropdown.beforeShow', openTooltip);
            
        var dropdown = dropdownFactory($occupancyBox, $tooltip);

        var $roomList = $("div.column-outer", $tooltip)
            .on('click', 'a.btn-clear-row', undefined, deleteRoom);

        var $occupancySelectionHtmlClone = $("div.column-inner", $roomList).detach();

        var $doneButton = $("#cp-done", $tooltip).on("click", function () {
            dropdown.loading.show();
            window.setTimeout(submitState, Math.random() * 1000);
        });

        var $closeButton = $(".close-btn", $tooltip).on("click", function () {
            dropdown.hide();
        });

        var $roomCountSelector = $("select.cp-select-numberOfRooms", $tooltip);

        var $occupancyBoxText = $("span", $occupancyBox);

        var $selectMenuContainer = $("div.ui-selectmenus", $tooltip);
        
        var tempOccupancyDuringSelect = null;

        stateMachine.attachListener(dropdown.hide);
        
        function closeTooltip() {
            tempOccupancyDuringSelect = null;
            updateUi();
        }

        function openTooltip() {
            tempOccupancyDuringSelect = utils.deepClone(stateMachine.selectedOccupancies);
            updateUi();
        }

        function updateSelection(e) {
            e.stopPropagation();

            if ($roomCountSelector.is(e.target)) {
                var newCount = parseInt($roomCountSelector.val());
                var maxOccupancyAdults = logic.maximumOccupancy.adults == 1 ? 1 : 2;

                if (newCount > tempOccupancyDuringSelect.length) {
                    for (var i = tempOccupancyDuringSelect.length; i < newCount; i++) {
                        tempOccupancyDuringSelect.push({
                            adults: maxOccupancyAdults, //maxOccupancyAdults
                            children: 0
                        });
                    }
                } else {
                    for (var i = tempOccupancyDuringSelect.length; i > newCount; i--) {
                        tempOccupancyDuringSelect.pop();
                    }
                }

            } else {
                var $target = $(e.target);
                var roomNum = $target.data('room');
                var type = $target.hasClass('cp-select-roomAdults') ? 'adults' : 'children';

                tempOccupancyDuringSelect[roomNum - 1][type] = parseInt($target.val());
            }

            updateUi();
        }

        function deleteRoom(e) {
            e.stopPropagation();

            var $target = $(e.target);

            if ($target.hasClass("disabled")) {
                return;
            }

            var roomNum = $target.data('room') - 1;

            tempOccupancyDuringSelect.splice(roomNum, 1);

            updateUi();
        }

        function submitState() {
            var occupancySelection = tempOccupancyDuringSelect;
            
            var compatibleRooms = logic.getCompatibleRoomTypesForOccupancies(occupancySelection);
            
            tempOccupancyDuringSelect = null;
            if (stateMachine.selectedDepartureDate !== null && logic.validator.validateCheckOutDate(compatibleRooms, stateMachine.selectedArrivalDate, stateMachine.selectedDepartureDate)) {
                stateMachine.transitionState("selected", stateMachine.selectedArrivalDate, stateMachine.selectedDepartureDate, occupancySelection, compatibleRooms);
            }
            else if (stateMachine.selectedArrivalDate !== null && logic.validator.validateCheckInDate(compatibleRooms, stateMachine.selectedArrivalDate)) {
                var wasInvalidCheckOutDate = stateMachine.selectedDepartureDate !== null;
                stateMachine.transitionState("departure", stateMachine.selectedArrivalDate, occupancySelection, compatibleRooms);

                if (wasInvalidCheckOutDate) {
                    $occupancySelector.trigger("calendar:error:checkOutDateReset");
                }
            } else {
                var wasInvalidDates = stateMachine.selectedArrivalDate !== null;
                stateMachine.transitionState("arrival", occupancySelection, compatibleRooms);

                if (occupancySelectorOptions != null && typeof (occupancySelectorOptions.calendarRefreshData) == "function") {
                    occupancySelectorOptions.calendarRefreshData(utils.addDays(utils.today(), -1));
                }

                if (wasInvalidDates) {
                    $occupancySelector.trigger("calendar:error:datesReset");
                }
            }
        }

        function updateUi() {
            var occupancy = tempOccupancyDuringSelect || stateMachine.selectedOccupancies;

            var noChildren = !logic.hasCompatibleRoomTypesForOccupancy({ adults: 0, children: 1 });
            $tooltip.toggleClass('no-children', noChildren);
            $tooltip.toggleClass('single-inventory', logic.maximumInventory <= 1);
            $occupancySelector.toggleClass('error', utils.any(occupancy, utils.not(logic.hasCompatibleRoomTypesForOccupancy)));

            updateRoomCountSelector(occupancy);
            updateDoneButton(occupancy);
            updateRoomSelectors(occupancy, noChildren);
            updateSummaryText(occupancy);
        }

        function updateRoomCountSelector(occupancy) {
            var range = utils.range(1, logic.maximumInventory);
            
            var options = utils.map(range, function (roomNum) {
                var text = "";
                if (roomNum > 1) {
                    text = resources.roomCountOptionPlural;
                } else {
                    text = resources.roomCountOption;
                }
                return '<option value="' + roomNum + '">' + utils.format(text, { roomNum: roomNum }) + '</option>';
            }).join("");

            if ($roomCountSelector.data('ui-selectmenu')) {
                $roomCountSelector.selectmenu('destroy');
            }

            $roomCountSelector
                .html(options)
                .val(occupancy.length)
                .selectmenu({ appendTo: $selectMenuContainer });
        }

        function updateDoneButton(occupancy) {
            var disabled = occupancy.length === 0 || utils.any(occupancy, utils.not(logic.hasCompatibleRoomTypesForOccupancy));

            $doneButton.prop('disabled', disabled);
        }

        function updateRoomSelectors(occupancy, noChildren) {
            var maxOccupancy = logic.maximumOccupancy;
            var adultOptions = utils.map(utils.range(1, maxOccupancy.adults), function (adultNum) {
                return '<option value="' + adultNum + '">' + utils.format(resources.adults, { NumAdults: adultNum }) + '</option>';
            }).join("");
            var childrenOptions = utils.map(utils.range(0, maxOccupancy.children), function (childrenNum) {
                return '<option value="' + childrenNum + '">' + utils.format(resources.children, { NumChildren: childrenNum }) + '</option>';
            }).join("");

            var rooms = utils.map(occupancy, function(occ, roomNum) {
                var $room = $occupancySelectionHtmlClone.clone();
                $room.toggleClass("error", !logic.hasCompatibleRoomTypesForOccupancy(occ));
                $room.find("span.room").text(utils.format(resources.roomTitle, { roomNum: roomNum + 1 }));
                $room.find("select.cp-select-roomAdults")
                    .html(adultOptions)
                    .data('room', roomNum + 1)
                    .val(occ.adults)
                    .selectmenu({ appendTo: $selectMenuContainer });
                $room.find("select.cp-select-roomChildren")
                    .html(childrenOptions)
                    .data('room', roomNum + 1)
                    .val(occ.children)
                    .prop('disabled', noChildren)
                    .selectmenu({ appendTo: $selectMenuContainer });
                $room.find('a.btn-clear-row')
                    .data('room', roomNum + 1)
                    .toggleClass('disabled', occupancy.length === 1)
                    .prop('disabled', occupancy.length === 1);
                return $room;
            });

            $roomList.empty().append(rooms);
        }

        function updateSummaryText(occupancy) {
            var newText = getSummaryText(occupancy);
            var oldText = $occupancyBoxText.text();
            if (newText !== oldText) {
                $occupancyBoxText
                    .finish()
                    .textFade(newText, { beforeShow: function() {
                        // Note: this is to force the browser to recalculate the width of the occupancy box container.
                        // Without it, a longer string is wrapped and a scrollbar appears until the next time the tooltip
                        // is shown
                        $tooltip.toggle().toggle();
                    }
                    });
            }
        }

        function getSummaryText(occupancy) {
            var text = "";

            if (utils.all(occupancy, function (o) { return o.children === 0; })) {
                if (occupancy.length > 1) {
                    text = resources.summaryFormatNoChildrenPlural;
                } else {
                    text = resources.summaryFormatNoChildren;
                }
                return utils.format(text,
                {
                    adults: utils.sum(occupancy, "adults"),
                    rooms: occupancy.length
                });
            } else {
                if (occupancy.length > 1) {
                    text = resources.summaryFormatPlural;
                } else {
                    text = resources.summaryFormat;
                }
                return utils.format(text,
                {
                    adults: utils.sum(occupancy, "adults"),
                    children: utils.sum(occupancy, "children"),
                    rooms: occupancy.length
                });
            }
        }

        return Object.freeze({
            updateUi: updateUi
        });
    };
})(window.jQuery, window.eviivo.availabilityCalendar.utils, window.eviivo.customDropdown);
"use strict";

window.eviivo = window.eviivo || {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};
window.eviivo.availabilityCalendar.ui = window.eviivo.availabilityCalendar.ui || {};

window.eviivo.availabilityCalendar.ui.roomFilter = (function($, utils, dropdownFactory) {
    
    return function($roomFilter, stateMachine, logic, resources) {

        var tempFilterDuringSelection = null;

        var $filterBox = $("span.cp-input", $roomFilter);
        var $filterBoxText = $("span.summary", $filterBox);
        var $tooltip = $("div.cp-tooltip", $roomFilter)
            .on('customDropdown.beforeShow', function () {
                tempFilterDuringSelection = stateMachine.selectedRoomTypes !== undefined
                    ? utils.deepClone(stateMachine.selectedRoomTypes)
                    : logic.getCompatibleRoomTypesForOccupancies(stateMachine.selectedOccupancies);
                updateUi();
            })
            .on('customDropdown.hide', function() {
                tempFilterDuringSelection = null;
                updateUi();
            });

        var dropdown = dropdownFactory($filterBox, $tooltip);
        var totalNumberRooms = logic.roomTypeIds.length;

        stateMachine.attachListener(dropdown.hide);

        var $closeButton = $(".close-btn", $tooltip).on("click", function () {
            dropdown.hide();
        });

        var $doneButton = $("#acFilter-roomType-apply", $tooltip).on("click", function() {
            dropdown.loading.show();
            window.setTimeout(submitChanges, Math.random() * 1000);
        });

        var $selectAllButton = $("#acFilter-roomType-selectAll", $tooltip).on("click", function () {
                tempFilterDuringSelection = logic.getCompatibleRoomTypesForOccupancies(stateMachine.selectedOccupancies);
                updateUi();
            });
        var $clearAllButton = $("#acFilter-roomType-clearAll", $tooltip).on("click", function () {
                tempFilterDuringSelection = [];
                updateUi();
            });

        var $requirementsSummaryText = $("p.requirementsSummary", $tooltip);

        var $checkBoxList = $("ul.mod-customDropdown-list", $tooltip)
            .on("click", "li", undefined, function (e) {
                e.stopPropagation();
                e.preventDefault();
                toggleCheckbox($(e.currentTarget));
            });
        var $checkBoxElem = $("li", $checkBoxList).detach();
        
        function toggleCheckbox($checkbox) {
            var selectable = $checkbox.data('selectable');
                if (!selectable) {
                    return;
                }

                var roomId = $checkbox.data('room');
                var index = tempFilterDuringSelection.indexOf(roomId);
                if (index !== -1) {
                    tempFilterDuringSelection.splice(index, 1);
                } else {
                    tempFilterDuringSelection.push(roomId);
                }

            updateUi();
        }

        function submitChanges() {
            var selectedRooms = tempFilterDuringSelection;
            tempFilterDuringSelection = null;

            if (stateMachine.selectedDepartureDate !== null && logic.validator.validateCheckOutDate(selectedRooms, stateMachine.selectedArrivalDate, stateMachine.selectedDepartureDate)) {
                stateMachine.transitionState("selected", stateMachine.selectedArrivalDate, stateMachine.selectedDepartureDate, stateMachine.selectedOccupancies, selectedRooms);
            } else if (stateMachine.selectedArrivalDate !== null && logic.validator.validateCheckInDate(selectedRooms, stateMachine.selectedArrivalDate)) {
                var wasInvalidDepartureDate = stateMachine.selectedDepartureDate !== null;
                stateMachine.transitionState("departure", stateMachine.selectedArrivalDate, stateMachine.selectedOccupancies, selectedRooms);

                if (wasInvalidDepartureDate) {
                    $roomFilter.trigger("calendar:error:checkOutDateReset");
                }
            } else {
                var wasInvalidDates = stateMachine.selectedArrivalDate !== null;
                stateMachine.transitionState("arrival", stateMachine.selectedOccupancies, selectedRooms);

                if (wasInvalidDates) {
                    $roomFilter.trigger("calendar:error:datesReset");
                }
            }
        }

        function updateUi() {
            var filter = tempFilterDuringSelection || stateMachine.selectedRoomTypes || logic.roomTypeIds;

            var selectableRooms = utils.numericSort(logic.getCompatibleRoomTypesForOccupancies(stateMachine.selectedOccupancies));
            var unselectableRooms = utils.numericSort(utils.setDifference(logic.roomTypeIds, selectableRooms));

            updateButtons(filter, selectableRooms);
            updateSummary(filter);
            updateRequirementsSummary(selectableRooms);
            totalNumberOfRoomIds();
            updateCheckboxes(filter, selectableRooms, unselectableRooms);
        }

        function updateButtons(filter, selectableRooms) {
            var disabled = !logic.isRoomSelectionAbleToSatisfyOccupancies(filter, stateMachine.selectedOccupancies);

            $doneButton
                .prop('disabled', disabled)
                .attr('title', disabled ? resources.selectionCannotSatisfyOccupancies : null);

            $selectAllButton.prop('disabled', filter === undefined || filter.length === selectableRooms.length);
            $clearAllButton.prop('disabled', filter !== undefined && filter.length === 0);
        }

        function updateSummary(filter) {
            var resource = "";
            if (totalNumberRooms > 0) {
                resource = resources.summaryFormatPlural;
            } else {
                resource = resources.summaryFormat;
            }
            var summaryText = utils.format(resource, { SelectedRooms: filter.length, TotalRooms: totalNumberRooms });

            if ($filterBoxText.text() !== summaryText) {
                $filterBoxText
                    .finish()
                    .textFade(summaryText, { beforeShow: function() {
                            // Note: this is to force the browser to recalculate the width of the occupancy box container.
                            // Without it, a longer string is wrapped and a scrollbar appears until the next time the tooltip
                            // is shown
                            $tooltip.toggle().toggle();
                        }
                    });
            }
        }

        function totalNumberOfRoomIds() {
            // make available rooms filter scrollable if it contains more than 6 rooms
            var $roomFilterContainer = $("div.column-roomFilter");
            if (totalNumberRooms > 6 ) {
                $roomFilterContainer.addClass("is--scrollable");
            }
            
        }

        function updateRequirementsSummary(selectableRooms) {
            var resource = "";
            if (selectableRooms.length > 1) {
                resource = resources.requirementsSummaryPlural;
            } else {
                resource = resources.requirementsSummary;
            }
            var text = utils.format(resource, { AvailableRooms: selectableRooms.length });
            $requirementsSummaryText.text(text);
        }

        function updateCheckboxes(filter, selectableRooms, unselectableRooms) {
            var roomTypeNames = logic.roomTypes;

            var selectableCheckboxes = $.map(selectableRooms, function(room) { return generateCheckbox(room, true)});
            var unselectableCheckboxes = $.map(unselectableRooms, function (room) { return generateCheckbox(room, false) });

            $checkBoxList.empty().append(selectableCheckboxes).append(unselectableCheckboxes);

            function generateCheckbox(room, enabled) {
                var $newCheckbox = $checkBoxElem.clone()
                    .data("room", room)
                    .data("selectable", enabled);

                var id = "roomFilter-roomType-" + room;

                $("label", $newCheckbox).attr('for', id);
                $("input.roomType", $newCheckbox)
                    .attr('id', id)
                    .prop('checked', filter.indexOf(room) !== -1)
                    .prop('disabled', !enabled);
                $("span.roomName", $newCheckbox).text(roomTypeNames[room]);

                return $newCheckbox;
            }
        }

        return Object.freeze({
            updateUi: updateUi
        });
    };
})(window.jQuery, window.eviivo.availabilityCalendar.utils, window.eviivo.customDropdown);
"use strict";

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar || {};
window.eviivo.availabilityCalendar.ui = window.eviivo.availabilityCalendar.ui || {};

window.eviivo.availabilityCalendar.ui.chrome = (function ($, utils, uuid) {

    return function (options, $searchBar, stateMachine) {

        var $checkInText = $("span.startDateTooltip", $searchBar).on("click", function () {
            if (!stateMachine.allowShowPrices) {
                errorTooltip($checkInText, options.resources.selectDatesError, 1500, "dates");
            }
        });

        var $checkOutText = $("span.endDateTooltip", $searchBar).on("click", function () {
            if (!stateMachine.allowShowPrices) {
                errorTooltip($checkOutText, options.resources.selectDatesError, 1500, "dates");
            }
        });

        var $startDate = $("#checkin-text", $searchBar).on("click", function () {
            if (!stateMachine.allowShowPrices) {
                errorTooltip($startDate, options.resources.selectDatesError, 1500, "dates");
            }
        });
        var $startDateText = $("span.date-format", $startDate);

        var $endDate = $("#checkout-text", $searchBar).on("click", function () {
            if (!stateMachine.allowShowPrices) {
                errorTooltip($endDate, options.resources.selectDatesError, 1500, "dates");
            }
        });
        var $endDateText = $("span.date-format", $endDate);
        var $clearDates = $("#clear-dates", $searchBar).on("click", function () {
            $searchBar.trigger('calendar:reset');
        });
        var $showCalendar = $(".cp-button-price-wrapper button.cp-button-text", $searchBar).on("click", function () {
            // when searchDone state applied, and calendar collapsed & user wants to change search dates to enable calendar default state adding class searchExpanded next to searchDone
            $searchBar.addClass("searchExpanded");
            $searchBar.trigger('calendar:refreshData');
        });
        var $numberOfNights = $("#numberOfNights span", $searchBar);

        var $searchButton = $("button.cp-button-price", $searchBar).on("click", function () {
            stateMachine.transitionState("searchDone", stateMachine.selectedArrivalDate, stateMachine.selectedDepartureDate, stateMachine.selectedOccupancies, stateMachine.selectedRoomTypes);
            $searchBar.trigger('calendar:submit');
            var $spinner = $("<div class='cp--loading'><span class='circle'></span></div>");
            var $roomOverlaySpinner = $(".results-item-group").append($spinner);
        });
        $searchButton.next(".button-overlay").on("click", function () { errorTooltip($searchButton, options.resources.selectDatesError, 1500, "button-overlay"); });

        $searchBar
            .on('calendar:error:checkOutDateReset', function (e) {
                errorTooltip($endDate, options.resources.checkOutIncompatible, 3000, "dates");
            })
            .on('calendar:error:datesReset', function (e) {
                errorTooltip($startDate, options.resources.checkInIncompatible, 3000, "dates");
            });

        var toolTipIdGuidPrefix = "chrome-tooltip-" + uuid() + "-";
        function errorTooltip($elem, text, duration, id) {
            duration = duration || 7500;

            var $existingTooltip = $("#" + toolTipIdGuidPrefix + id)
                .remove();

            $elem.tooltip({
                content: function () {
                    return utils.splitIntoHtmlLines(text, '|');
                },
                items: "*",
                position: {
                    my: "center " + "bottom-15",
                    at: "center " + "top+10",
                    using: function (position, feedback) {
                        $(this).css(position);
                        $("<div>")
                            .addClass("arrow")
                            .addClass(feedback.vertical)
                            .prependTo(this);
                    }
                },
                show: false,
                hide: false,
                open: function (event, ui) {
                    var $tooltip = $(ui.tooltip);
                    var $elem = $(this);

                    var tooltipOffset = $tooltip.offset();
                    var cellOffset = $elem.offset();

                    $elem.append($tooltip);

                    $tooltip
                        .css('left', (tooltipOffset.left - cellOffset.left) + "px")
                        .css('top', (tooltipOffset.top - cellOffset.top) + "px");
                }
            });

            $elem.tooltip('open');
            var tooltips = $elem.data('ui-tooltip').tooltips;
            var tooltipId = Object.getOwnPropertyNames(tooltips)[0];
            var $tooltipElem = $('#' + tooltipId)
                .clone(false, false)
                .attr("id", toolTipIdGuidPrefix + id)
                .addClass("tooltip-error")
                .css("z-index", utils.MAX_SAFE_INTEGER)
                .appendTo($elem);
            $elem.tooltip("destroy");
            $tooltipElem
                .delay(duration / 5)
                .removeClass("tooltip-error", {
                    duration: duration * 2 / 5, easing: "swing", complete: function () {
                        $tooltipElem.animate({ opacity: 0 }, {
                            duration: duration * 2 / 5, easing: "swing", complete: function () {
                                $tooltipElem.remove();
                            }
                        });
                    }
                });
        }

        function formatDate(date) {
            var dateObj;
            if (date instanceof Date) {
                dateObj = date;
            } else if (typeof (date) === "string" && utils.dateRegex.test(date)) {
                dateObj = utils.parseDate(date);
            } else {
                throw "Unknown date format";
            }

            var format = options.resources.dateDisplayFormat;
            return $.datepicker.formatDate(format, dateObj);
        };

        function updateUi() {
            $searchButton.prop('disabled', !stateMachine.allowShowPrices).toggleClass('disabled', !stateMachine.allowShowPrices);
            $clearDates.prop('disabled', !stateMachine.allowClearDates).toggleClass('disabled', !stateMachine.allowClearDates);

            $startDateText.text(stateMachine.selectedArrivalDate ? formatDate(stateMachine.selectedArrivalDate) : "");
            $endDateText.text(stateMachine.selectedDepartureDate ? formatDate(stateMachine.selectedDepartureDate) : "");

            if (stateMachine.selectedArrivalDate && stateMachine.selectedDepartureDate) {
                $numberOfNights.text(utils.nightsBetween(stateMachine.selectedArrivalDate, stateMachine.selectedDepartureDate));
            } else {
                $numberOfNights.text("");
            }
        }

        function triggerSearch() {
            $searchBar.trigger('calendar:submit');
        }

        return Object.freeze({
            updateUi: updateUi,
            triggerSearch: triggerSearch
        });
    };

})(jQuery, window.eviivo.availabilityCalendar.utils, eviivo.utils.uuid);

window.eviivo = window.eviivo ? window.eviivo : {};
window.eviivo.availabilityCalendar = window.eviivo.availabilityCalendar ? window.eviivo.availabilityCalendar : {};
window.eviivo.availabilityCalendar.ui = window.eviivo.availabilityCalendar.ui ? window.eviivo.availabilityCalendar.ui : {};

window.eviivo.availabilityCalendar.ui.main = function ($, utils, stateMachineFactory, occupancySelectorFactory, chromeFactory, roomFilterFactory, responsiveCalendarFactory, urlHandler) {

    // Add an afterShow callback to allow code to run after refreshing the HTML view
    $(function() {
        $.datepicker._updateDatepicker_original = $.datepicker._updateDatepicker;
        $.datepicker._updateDatepicker = function(inst) {
            $.datepicker._updateDatepicker_original(inst);
            var afterShow = this._get(inst, 'afterShow');
            if (afterShow)
                afterShow.apply((inst.input ? inst.input[0] : null)); // trigger custom callback
        }
    });

    var defaultOptions = Object.freeze({
            roomsLimit: 3, //default room limit is 3 if not overriden by external settings
            eviivoSearchBoxId: "#eviivo-availability-search", //default serach box id is "eviivo-availability-search" if not overriden by external settings
            cultureLanguageCode: "en-GB",
            baseSearchUrl: "", //have this search criteria as a default for easier way to parse the query string and easier way to customize the search if properties want to
            preloadMonths: 6
        });

    return function(logic, ajax, settings) {
        var options = utils.immutableDeepClone($.extend({}, defaultOptions, settings));

        var stateMachine = generateStateMachine(urlHandler.extractCriteriaFromUrl(window.location.search));
        
        var $searchBar = $(options.eviivoSearchBoxId);

        var responsiveCalendar = responsiveCalendarFactory($(".calendar-display", $searchBar), options, stateMachine, logic, ajax);
        
        var occupancySelector = occupancySelectorFactory(
            $("div.column-occupancy", $searchBar),
            stateMachine,
            logic,
            options.resources.occupancySelector,
            {
                calendarRefreshData: responsiveCalendar.refreshData
            }
        );

        var chrome = chromeFactory(options, $searchBar, stateMachine);

        var roomFilter = roomFilterFactory(
            $("div.column-roomFilter", $searchBar),
            stateMachine,
            logic,
            options.resources.roomFilter
        );

        setCalendarDailyRefreshTimer(updateUi);

        window.addEventListener("resize", updateUiSetup);

        function updateUiSetup() {
            var queue = eviivo.util.debounce({ func: updateUi, unique: "updateUi" });
        }

        logic.attachListener(stateMachine.forceRefresh);
        logic.attachListener(updateUi);
        stateMachine.attachListener(updateUi);

        $searchBar.on('calendar:submit', navigateToSearchUrl);
        $searchBar.on('calendar:reset', function () {
            stateMachine.transitionState("arrival", stateMachine.selectedOccupancies, stateMachine.selectedRoomTypes);

            responsiveCalendar.refreshData(utils.addDays(utils.today(), -1));
        });

        $searchBar.on('calendar:refreshData', function () {
            responsiveCalendar.refreshData(null);
        });
        
        function updateUi() {
            $searchBar.removeClass("pre-ui-init");
            $searchBar.removeClass(stateMachine.stateNames.join(" ")).addClass(stateMachine.stateName);
            chrome.updateUi();
            roomFilter.updateUi();
            occupancySelector.updateUi();
            responsiveCalendar.updateUi();
        }

        function triggerSearch() {
            chrome.triggerSearch();
        }

        function navigateToSearchUrl() {
            var criteria = {
                startDate: stateMachine.selectedArrivalDate,
                endDate: stateMachine.selectedDepartureDate,
                occupancy: stateMachine.selectedOccupancies,
                roomFilter: stateMachine.selectedRoomTypes
            };

            var newQueryString = urlHandler.generateNewUrlFromCriteria(window.location.search, criteria, $("#ref").val());
            window.location.assign(options.baseSearchUrl + newQueryString);
        }

        function setCalendarDailyRefreshTimer(func) {
            if (typeof (func) !== "function") {
                throw "func must be a function";
            }

            var tomorrow = utils.parseDate(utils.addDays(utils.today(), 1));

            // Add an extra half second to make sure we're fired on the next day
            var millisecondsUntilTomorrow = tomorrow.getTime() + 500 - new Date().getTime();

            window.setTimeout(function() {
                func();
                setCalendarDailyRefreshTimer(func);
            }, millisecondsUntilTomorrow);
        }

        function generateStateMachine(urlCriteria) {
            var occupancy = (function() {
                if (logic.validator.validateOccupancies(urlCriteria.occupancy)) {
                    return urlCriteria.occupancy;
                }

                var cleanedOccupancies = logic.validator.cleanOccupancies(urlCriteria.occupancy);

                if (logic.validator.validateOccupancies(cleanedOccupancies)) {
                    return cleanOccupancies;
                }

                return Object.freeze([Object.freeze({ adults: 1, children: 0 })]);
            })();

            var roomFilter = (function() {
                if (logic.validator.validateRoomTypes(occupancy, urlCriteria.roomFilter)) {
                    return urlCriteria.roomFilter;
                }

                var cleanedRoomFilters = logic.validator.cleanRoomTypes(occupancy, urlCriteria.roomFilter);

                if (logic.validator.validateRoomTypes(occupancy, cleanedRoomFilters)) {
                    return cleanedRoomFilters;
                }

                return undefined;
            })();

            var checkInDate = urlCriteria.startDate && urlCriteria.startDate >= utils.today()
                ? urlCriteria.startDate
                : null;

            var checkOutDate = urlCriteria.endDate && checkInDate !== null && urlCriteria.endDate > checkInDate
                ? urlCriteria.endDate
                : null;

            try {
                if (checkInDate === null && checkOutDate === null) {
                    return stateMachineFactory(options, logic, "arrival", occupancy, roomFilter);
                } else if (checkInDate !== null && checkOutDate === null) {
                    return stateMachineFactory(options, logic, "departure", checkInDate, occupancy, roomFilter);
                } else if (checkInDate === null) {
                    throw "Logic broken - check out date cannot be not null with a null check in date";
                } else {
                    return stateMachineFactory(options, logic, "searchDone", checkInDate, checkOutDate, occupancy, roomFilter);
                }
            } catch (e) {
                window.Sentry.captureException(e);
                throw "Availability calendar broken - Details:" + e;
            }
        }
        
        return Object.freeze({
            updateUi: updateUi,
            triggerSearch: triggerSearch
        });
    };

}(jQuery, window.eviivo.availabilityCalendar.utils, eviivo.availabilityCalendar.ui.stateMachine, eviivo.availabilityCalendar.ui.occupancySelector, eviivo.availabilityCalendar.ui.chrome, eviivo.availabilityCalendar.ui.roomFilter, eviivo.availabilityCalendar.ui.responsiveCalendar, eviivo.availabilityCalendar.ui.urlHandler);
