﻿saffire.angular.app.directive('sfSeatSelector', ['$rootScope', '$http', '$interval', '$anchorScroll', '$location', 'saffire.angular.webServices', 'saffire.angular.webPoster', '$window', '$timeout', '$q', 'reservedSeatingService', function ($rootScope, $http, $interval, $anchorScroll, $location, webServices, webPoster, $window, $timeout, $q, reservedSeatingService) {

    function link(scope, element, attrs) {

        scope.searchButtonText = 'Search';
        scope.chart = undefined;
        scope.selector.holdMode = 'bestAvailable';
        scope.selector.isIdle = true;
        scope.showMapOnBestAvailable = false;
        scope.initialized = false;
        scope.seatmapPricingEnabled = false;
        scope.holdErrors = 0;
        scope.holdTokenIsActivating = false;
        scope.selectionGroups = [];

        if (!scope.selector) {
            scope.selector = {};
        }

        if (scope.selector) {
            scope.selector.currentHolds = [];
            scope.selector.expiredHolds = [];
            scope.selector.gaAreasHavingGAHolds = [];
        }


        //debug

        scope.debug = function () {


            scope.resetChartAndToken();

        }

        //UI helpers

        scope.selector.selectObjects = function (objects,ignoreHoldErrors) {

            //ignoreHoldErrors should be set to true if you are selecting seats that have already been held
            //like in a case were you have held the seats on server side but still need to have them
            //selected in the selector

            if (ignoreHoldErrors) {
                scope.ignoreHoldErrors = true;
            }
            scope.chart.selectObjects(objects)

            if (ignoreHoldErrors) {
                $timeout(function () {
                    scope.ignoreHoldErrors = undefined;
                }, 3000)
            }

        }

        scope.selector.displayHolds = function () {

            var categories = [];

            if (scope.selector && scope.selector.options && scope.selector.options.seatmap) {
                for (c = 0; c < scope.selector.options.seatmap.Categories.length; c++) {

                    var category = scope.selector.options.seatmap.Categories[c];

                    var seats = [];
                    seats = $.grep(scope.selector.currentHolds, function (r) {
                        return r.categoryKey == category.Key;
                    });
                    category.seats = seats;

                    var expiredSeats = [];
                    expiredSeats = $.grep(scope.selector.expiredHolds, function (r) {
                        return r.categoryKey == category.Key;
                    });
                    category.expiredSeats = expiredSeats;

                    if (category.seats.length || category.expiredSeats.length) {
                        categories.push(category);
                    }

                }
            }

            return categories;
        }

        scope.secondsToTime = function (seconds) {

            return secondsToTime(seconds);

        }

        scope.cartButtonText = function () {
            if (scope.selector.options && scope.selector.options.cartItem) {
                return "Update Cart"
            }
            else {
                return "Add to Cart"
            }
        }

        //Hold best available

        scope.searchBestAvailable = function () {

            if (scope.categorySelection == "Select...") {
                scope.showMessage({
                    autoHide: false,
                    message: 'Please select a category.',
                    buttons: [
                        {
                            text: 'OK',
                            onClick: function () {
                                scope.closeMessage();
                            }
                        }
                    ],
                });
            }
            else {
                var config = {
                    number: scope.qtySelection,
                    category: scope.categorySelection
                }
                
                if (scope.selector.options.cartItem) {

                    scope.showMessage({
                        autoHide: false,
                        message: 'Searching for new seats will remove the current seats in your cart',
                        buttons: [
                            {
                                text: 'Continue',
                                onClick: function () {
                                    scope.closeMessage();
                                    scope.enabled = false;
                                    scope.selector.options.removeFromCart(scope.selector.options.productID, true).then(function (success) {
                                        scope.enabled = true;
                                        scope.selector.options.cartItem = null;
                                        scope.selector.options.updateCartIndicator();
                                        if (success == true) {
                                            scope.selector.options.holdTokenIsActive = false;
                                            scope.chart.selectBestAvailable(config);
                                        }
                                        else {
                                            scope.showMessage({
                                                autoHide: false,
                                                message: 'An error occurred while removing the seats from your cart. Please refresh the page and try again',
                                                buttons: [
                                                    {
                                                        text: 'Refresh Page',
                                                        onClick: function () {
                                                            scope.reloadPage();
                                                        }
                                                    }
                                                ],
                                            });
                                        }
                                    });
                                }
                            },
                            {
                                text: 'Cancel',
                                onClick: function () {
                                    scope.closeMessage();
                                }
                            }
                        ],
                    });

                }
                else {
                    scope.enabled = false;

                    scope.selector.options.holdTokenIsActive = false;
                    scope.chart.selectBestAvailable(config);

                    scope.notEnoughSeatsInBestAvailable = $timeout(function () {

                        if (!scope.enabled) {

                            scope.notEnoughSeatsInBestAvailable = null;

                            var currentSelections = scope.chart.selectedObjects.length;
                            var numberSearched = config.number;

                            if (numberSearched > currentSelections) {

                                scope.showMessage({
                                    autoHide: false,
                                    message: 'Only ' + currentSelections.toString() + ' seats are available for this category',
                                    buttons: [
                                        {
                                            text: 'Continue',
                                            onClick: function () {
                                                scope.enabled = true;
                                                scope.closeMessage();
                                            }
                                        }
                                    ],
                                });
                            }

                            
                        }

                    }, 2000)
                }
            }

        }

        //Group selection helpers
        scope.getSeatNeighborLabel = function (seatObject, direction) {

            var sectionID = seatObject.labels.section;
            var rowID = seatObject.labels.parent;
            var seatID = seatObject.labels.own;

            var neighbor = null;

            if (IsNumeric(seatID)) {

                neighbor = scope.constructSeatLabel((parseInt(seatID) + direction).toString(), rowID, sectionID)

            }

            return neighbor;
        }

        scope.constructSeatLabel = function (seatID, parentID, sectionID) {

            var seatLabels = [];

            if (sectionID) {
                seatLabels.push(sectionID.toString());
            }

            if (parentID) {
                seatLabels.push(parentID.toString());
            }

            seatLabels.push(seatID.toString())


            return seatLabels.join('-')

        }

        scope.getGroupSelectionSeatObject = function (seatID) {

            return {
                seatID: seatID,
                findInitiated: false,
                objectFound: false,
                objectNotFound: false,
                selectable: false,
                seatObjectIsSelectable: false,
            }

        }

        scope.addSelectionGroup = function (seatObject, needToSelectSeats) {

            if ($.grep(scope.selectionGroups, function (sg) {
                return sg.seatID == seatObject.id;
            }).length == 0) {

                var groupSelectionSeatObject = scope.getGroupSelectionSeatObject(seatObject.id)

                groupSelectionSeatObject.objectFound = true;
                groupSelectionSeatObject.selectable = true;
                groupSelectionSeatObject.findInitiated = true;

                var selectionGroup = {
                    seatID: seatObject.id,
                    direction: 1,
                    seats: [groupSelectionSeatObject],
                    labels: seatObject.labels,
                    needToSelectSeats: needToSelectSeats
                }

                var nextLabel = scope.getSeatNeighborLabel(seatObject, 1)
                var nextSeatObject = scope.getGroupSelectionSeatObject(nextLabel)
                nextSeatObject.findInitiated = true;

                selectionGroup.seats.push(nextSeatObject);

                scope.selectionGroups.push(selectionGroup);

                scope.chart.findObject(nextLabel, scope.groupSelectionSeatFound, scope.groupSelectionSeatNotFound);

            }

        }

        scope.groupSelectionSeatFound = function (seatObject) {

            scope.$apply(function () {

                angular.forEach(scope.selectionGroups, function (sg) {
                    angular.forEach(sg.seats, function (s) {
                        if (s.seatID == seatObject.id) {
                            s.objectFound = true;

                            //a seat is considered selectable if it isn't held or booked
                            //still want to consider it selectable even if currently held in another cart

                            var selectable = true

                            if (seatObject.status == 'hold' || seatObject.status == 'booked' || !seatObject.forSale) {
                                selectable = false;
                            }

                            s.selectable = selectable
                            s.seatObjectIsSelectable = seatObject.selectable
                            scope.handleNextGroupSelection(sg, s, seatObject);
                        }
                    })
                })

            })

        }

        scope.groupSelectionSeatNotFound = function (text) {

            scope.$apply(function () {

                var seatID = text.replace('Object not found: ', '');

                angular.forEach(scope.selectionGroups, function (sg) {
                    angular.forEach(sg.seats, function (s) {
                        if (s.seatID == seatID) {
                            s.objectNotFound = true;
                            scope.handleNextGroupSelection(sg, s, null);
                        }
                    })
                })

            })
        }

        scope.handleNextGroupSelection = function (selectionGroup, selectionGroupSeat, seatObject) {

            if (selectionGroupSeat.objectFound && selectionGroupSeat.selectable) {
                var nextLabel = scope.getSeatNeighborLabel(seatObject, selectionGroup.direction);
                selectionGroup.seats.push(scope.getGroupSelectionSeatObject(nextLabel));
                scope.chart.findObject(nextLabel, scope.groupSelectionSeatFound, scope.groupSelectionSeatNotFound);
            }
            else {
                if (selectionGroup.direction == 1) {
                    selectionGroup.direction = -1
                    var nextLabel = scope.getSeatNeighborLabel(selectionGroup, selectionGroup.direction);
                    selectionGroup.seats.push(scope.getGroupSelectionSeatObject(nextLabel));
                    scope.chart.findObject(nextLabel, scope.groupSelectionSeatFound, scope.groupSelectionSeatNotFound);
                }
                else {

                    //At this point, all consecutive selectable seats in a row have been found

                    //trim out the unavailable seats
                    selectionGroup.seats = $.grep(selectionGroup.seats, function (s) {
                        return s.objectFound && s.selectable;
                    })

                    if (selectionGroup.needToSelectSeats) {

                        //select the rest of the seats in the group
                        var selectSeats = [];

                        angular.forEach(selectionGroup.seats, function (s) {
                            if (s.seatObjectIsSelectable) {
                                selectSeats.push(s.seatID);
                            }
                            
                        })

                        scope.selector.selectObjects(selectSeats, true);

                    }

                    return;
                }
            }

        }

        //chart events

        scope.isObjectSelectable = function (object, defaultValue, extraConfig) {


            if (object.type == 'generalAdmission' && extraConfig.productType == 10) {
                return false;
            }

            
            if (object.type == 'generalAdmission') {

                if (extraConfig.generalAdmissions) {

                    var gaArea = $.grep(extraConfig.generalAdmissions, function (ga) {
                        return ga.Name == object.id
                    })

                    if (gaArea.length) {
                        gaArea = gaArea[0];

                        //determine if all seats in a GA area have been held
                        var seatsAllHeld = false;

                        if (gaArea.Products) {

                            for (p = 0; p < gaArea.Products.length; p++) {
                                var product = gaArea.Products[p];

                                var numHolds = 0;
                                if (product.Holds) {

                                    for (h = 0; h < product.Holds.length; h++) {
                                        var productHold = product.Holds[h];
                                        numHolds += productHold.Qty;
                                    }
                                }

                                if (object.numBooked + numHolds >= object.capacity) {
                                    seatsAllHeld = true;
                                }
                            }
                        }

                        if (seatsAllHeld) {
                            return false;
                        }
                        
                    }

                }

            }

            var categoryKey = object.category ? object.category.key : null;

            if (!categoryKey) {
                // not selectable if there is no category
                return false;
            }

            if (extraConfig && extraConfig.categories) {

                for (c = 0; c < extraConfig.categories.length; c++) {
                    if (extraConfig.categories[c].Key == categoryKey) {

                        var configCategory = extraConfig.categories[c];

                        if (!configCategory.TicketTypes || configCategory.TicketTypes.length == 0) {
                            return false;
                        }

                        break;
                    }
                }
            }

            //check object status
            var isSeasonTicketHoldForAnotherProduct = false;
            var hasBooked = false;
            var hasReserved = false;
            var objectsStatus = null;


            if (extraConfig.selectionMode == 'Onhold') {

                for (e = 0; e < extraConfig.events.length; e++) {

                    var currentEvent = extraConfig.events[e];

                    objectEventData = object.dataPerEvent[currentEvent]
        
                    if (objectEventData.extraData && objectEventData.extraData.holdTypeID && objectEventData.extraData.seasonTicketProductID && (objectEventData.extraData.seasonTicketProductID != extraConfig.productID)) {
                        isSeasonTicketHoldForAnotherProduct = true;
                    }

                    if (objectEventData.status == 'booked') {
                        hasBooked = true;
                    }

                    if (objectEventData.status == 'reservedByToken') {
                        hasReserved = true;
                    }

                    if (e == 0) {
                        objectsStatus = objectEventData.status;
                    }
                    else {
                        if (objectEventData.status != objectsStatus) {
                            objectsStatus = 'mixed';
                        }
                    }
                }

                var selectableStatuses = ['hold'];

                return !isSeasonTicketHoldForAnotherProduct && !hasBooked && !hasReserved && selectableStatuses.indexOf(objectsStatus) > -1

            }


            return defaultValue;
        }
        scope.selector.options.chartOptions.isObjectSelectable = scope.isObjectSelectable;

        scope.objectIcon = function (object, defaultIcon, extraConfig) {

            if (extraConfig.selectionMode == 'Onhold') {
                if (object.objectType == 'section') {
                    return '';
                }

                if (!object.selectable) {

                    if (object.objectType == "GeneralAdmissionArea") {

                        if (object.forSale && object.status == "free") {
                            return defaultIcon;
                        }

                    }

                    return extraConfig.nonSelectableIcon
                }

                var objectsStatus = null;
                var objectsHoldType = null;
                var isSeasonTicketHoldForAnotherProduct = false;
                var hasBooked = false;
                var hasReserved = false;

                for (e = 0; e < extraConfig.events.length; e++) {

                    var currentEvent = extraConfig.events[e];

                    objectEventData = object.dataPerEvent[currentEvent]

                    if (objectEventData.extraData && objectEventData.extraData.holdTypeID && objectEventData.extraData.seasonTicketProductID && (objectEventData.extraData.seasonTicketProductID != extraConfig.productID)) {
                        isSeasonTicketHoldForAnotherProduct = true;
                    }

                    if (objectEventData.status == 'booked') {
                        hasBooked = true;
                    }

                    if (objectEventData.status == 'reservedByToken') {
                        hasReserved = true;
                    }

                    if (e == 0) {
                        objectsStatus = objectEventData.status;
                        if (objectEventData.extraData && objectEventData.extraData.holdTypeID) {
                            objectsHoldType = objectEventData.extraData.holdTypeID.toString();
                        }
                    }
                    else {
                        if (objectEventData.status != objectsStatus) {
                            objectsStatus = 'mixed';
                        }
                        if (objectEventData.extraData && objectEventData.extraData.holdTypeID && objectEventData.extraData.holdTypeID != objectsHoldType) {
                            objectsHoldType = 'mixed';
                        }
                    }

                }

                //if we are in info mode then still return non-selectable icon
                if (isSeasonTicketHoldForAnotherProduct || hasBooked || hasReserved) {
                    return extraConfig.nonSelectableIcon
                }

                if (objectsStatus == 'mixed' || objectsHoldType == 'mixed') {
                    return extraConfig.mixedStatusIcon;
                }

                if (object.status != 'free' && object.status != 'hold') {

                    return defaultIcon;

                }
                else {

                    var icon = '';

                    var holdTypeID = 0;

                    if (object.extraData) {
                        holdTypeID = object.extraData.holdTypeID;
                    }

                    if (holdTypeID != null && holdTypeID != 0) {
                        if (object.selected) {
                            icon = 'check';
                        }
                        else {
                            icon = 'circle-o';
                        }

                    }

                    return icon;

                }
            }

            return defaultIcon;

        }
        scope.selector.options.chartOptions.objectIcon = scope.objectIcon;

        scope.objectColor = function (object, defaultColor, extraConfig) {

            if (extraConfig.selectionMode == 'Onhold') {
                if (!object.selectable) {

                    if (object.objectType == "GeneralAdmissionArea") {

                        if (object.forSale && object.status == "free" && object.category) {
                            return object.category.color;
                        }

                    }

                    return extraConfig.nonSelectableColor;

                }
                else {

                    var objectsStatus = null;
                    var objectsHoldType = null;

                    for (e = 0; e < extraConfig.events.length; e++) {

                        var currentEvent = extraConfig.events[e];

                        objectEventData = object.dataPerEvent[currentEvent]

                        if (e == 0) {
                            objectsStatus = objectEventData.status;
                            if (objectEventData.extraData && objectEventData.extraData.holdTypeID) {
                                objectsHoldType = objectEventData.extraData.holdTypeID.toString();
                            }
                        }
                        else {
                            if (objectEventData.status != objectsStatus) {
                                objectsStatus = 'mixed';
                            }

                            if (objectEventData.extraData && objectEventData.extraData.holdTypeID) {
                                if (!objectsHoldType) {
                                    objectsHoldType = objectEventData.extraData.holdTypeID.toString();
                                }
                                else if (objectEventData.extraData.holdTypeID != objectsHoldType) {
                                    objectsHoldType = 'mixed';
                                }
                            }

                        }

                    }

                    if (objectsStatus == 'mixed' || objectsHoldType == 'mixed') {
                        return extraConfig.mixedStatusColor;
                    }


                    if (object.status == 'mixed' || object.status == 'mixed') {
                        return extraConfig.mixedStatusColor;
                    }

                    if ((object.status == 'booked' || object.status == 'reservedByToken')) {
                        return extraConfig.nonSelectableColor;
                    }

                    var holdTypeColor = extraConfig.nonSelectableColor;

                    if (object.status == 'hold') {
                        var holdTypeID = 0;

                        if (object.extraData) {
                            holdTypeID = object.extraData.holdTypeID;
                        }

                        if (holdTypeID != null && holdTypeID != 0) {
                            var inActiveHold = $.grep(extraConfig.holdTypes, function (holdType) {
                                return (holdType.ID == holdTypeID);
                            }).length == 0

                            if (inActiveHold)
                                return extraConfig.inActiveHoldColor;
                        }

                        for (h = 0; h < extraConfig.holdTypes.length; h++) {
                            if (extraConfig.holdTypes[h].ID == holdTypeID) {
                                holdTypeColor = '#' + extraConfig.holdTypes[h].Color;
                            }
                        }
                    }

                    return holdTypeColor;
                }
            }
        };
        scope.selector.options.chartOptions.objectColor = scope.objectColor

        scope.objectSelected = function (seatObject) {

            scope.$apply(function () {

                if (scope.selector.options.enforceGroupSelection && scope.selector.chartIsLoaded && seatObject.objectType != "GeneralAdmissionArea") {
                    if (!$.grep(scope.selectionGroups, function (sg) {
                        return sg.seatID == seatObject.id || $.grep(sg.seats,function(s){ return s.seatID == seatObject.id}).length;
                    }).length) {
                        scope.addSelectionGroup(seatObject,true);
                    }
                    
                }

                scope.selector.expiredHolds = [];

                var ticketTypeID = null;

                if (seatObject.selectedTicketType) {
                    //price was selected from the seatmap

                    ticketTypeID = scope.getPriceTypeIDByName(seatObject.category.key, seatObject.selectedTicketType)
                }
                else {

                    //If this is pre-populating from the cart item then get the ticket type
                    if (scope.selector.options.cartItem) {
                        for (c = 0; c < scope.selector.options.cartItem.Categories.length; c++) {
                            var cartCategory = scope.selector.options.cartItem.Categories[c];

                            if (ticketTypeID == null) {
                                if (seatObject.objectType == "GeneralAdmissionArea") {

                                    var notAccessedGASeats = $.grep(cartCategory.GeneralAdmissions, function (gaSeat) {
                                        return gaSeat.Accessed == undefined
                                    });

                                    for (s = 0; s < notAccessedGASeats.length; s++) {
                                        var cartSeat = notAccessedGASeats[s];

                                        if (ticketTypeID == null && cartSeat.GeneralAdmissionID == seatObject.label) {
                                            ticketTypeID = cartSeat.TicketTypeID;
                                            cartSeat.Accessed = true;
                                        }
                                    }

                                } else {

                                    for (s = 0; s < cartCategory.Seats.length; s++) {
                                        var cartSeat = cartCategory.Seats[s];

                                        if (ticketTypeID == null && cartSeat.SeatID == seatObject.label) {
                                            ticketTypeID = cartSeat.TicketTypeID;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    else {

                        //If the user had clicked "retry" then scope.seatsToHold will be populated for a few seconds
                        //let's see if we can determine a selected ticket type from that

                        if (scope.expiredSeatsReference) {

                            var expiredSeat = $.grep(scope.expiredSeatsReference, function (sth) {
                                return sth.label == seatObject.label;
                            })

                            expiredSeat = expiredSeat.length ? expiredSeat[0] : null;

                            if (expiredSeat && expiredSeat.ticketType) {
                                ticketTypeID = expiredSeat.ticketType
                            }

                        }
                    }
                }

                scope.pushSeatsIOSeatToHold(seatObject, ticketTypeID);

                if (seatObject.objectType == "GeneralAdmissionArea") {
                    scope.updateGASeatSelectionInGAArea(seatObject);
                }

                if (scope.selector.options.chartOptions.extraConfig.selectionMode != 'Onhold') {
                    if (!scope.selector.options.holdTokenIsActive && !scope.holdTokenIsActivating) {

                        scope.holdTokenIsActivating = true;
                        scope.selector.isIdle = true;
                        var environment = scope.selector.options.environment == 'Kiosk' ? scope.selector.options.environment + ',AddToCart' : scope.selector.options.environment;

                        reservedSeatingService.startHoldToken(scope.selector.options.productID, environment).then(function (expiration) {

                            scope.holdTokenIsActivating = false;

                            if (expiration.ExceptionType) {

                                var exceptionMessage = 'An error has occurred. Please refresh the page and try again';

                                if (expiration.ExceptionType.search('BLLException') && expiration.ExceptionMessage) {
                                    exceptionMessage = expiration.ExceptionMessage;
                                }

                                $timeout(function () {
                                    scope.showMessage({
                                        autoHide: false,
                                        message: exceptionMessage,
                                        buttons: [
                                            {
                                                text: 'Refresh Page',
                                                onClick: function () {
                                                    scope.reloadPage();
                                                }
                                            }
                                        ],
                                    });

                                }, 500)
                            }
                            else {
                                scope.selector.options.holdToken.ExpiresInSeconds = expiration - 5; //take a little time off to account for response time
                                scope.selector.startTime = new Date();
                                scope.selector.isIdle = false;
                                scope.suppressLoadingMask = false;
                                scope.processTimer();
                                scope.selector.options.holdTokenIsActive = true;
                            }


                        })
                    }
                    else {
                        if (scope.enabled) {
                            scope.selector.isIdle = false;
                        }

                    }
                }
            })

        }
        scope.selector.options.chartOptions.onObjectSelected = scope.objectSelected;

        scope.selector.supressRemovingFromCartOnDeselection = false;

        scope.objectDeselected = function (seatObject) {

            scope.$apply(function () {

                var gaSeatID = "";
                var gaID = "";

                if (seatObject.objectType == 'GeneralAdmissionArea') {

                    var gaArea = scope.removeGASeatSelectionFromGAArea(seatObject);
                    gaSeatID = gaArea.gaSeatID;
                    gaID = gaArea.id;
                }

                if (seatObject.objectType != 'GeneralAdmissionArea') {

                    scope.selector.currentHolds = $.grep(scope.selector.currentHolds, function (r) {
                        return r.id != seatObject.label;
                    })
                }

                //see if we need to remove from cart

                var seatRemoved = false;

                if (scope.selector.options.cartItem && !scope.selector.supressRemovingFromCartOnDeselection) {

                    if (scope.selector.options.cartItem && scope.selector.options.cartItem.Categories) {

                        var categoryCount = scope.selector.options.cartItem.Categories.length;

                        for (c = categoryCount - 1; c >= 0; c--) {

                            if (seatRemoved) {
                                break;
                            }

                            var category = scope.selector.options.cartItem.Categories[c];

                            for (s = category.Seats.length - 1; s >= 0; s--) {
                                var seat = category.Seats[s];
                                if (seat.SeatID == seatObject.label) {
                                    scope.removeCartItem(seat);
                                    seatRemoved = true;
                                    break;
                                }
                            }

                            for (s = category.GeneralAdmissions.length - 1; s >= 0; s--) {
                                var seat = category.GeneralAdmissions[s];

                                if (seat.GeneralAdmissionID == gaID) {

                                    scope.removeCartItem(seat);
                                    seatRemoved = true;
                                    break;
                                }
                            }
                        }

                    }

                }

                if (scope.selector.options.enforceGroupSelection && seatObject.objectType != "GeneralAdmissionArea") {

                    //deselect any seats in the group
                    var selectionGroup = $.grep(scope.selectionGroups, function (sg) {
                        return sg.seatID == seatObject.id || $.grep(sg.seats, function (s) { return s.seatID == seatObject.id }).length;
                    })

                    if (selectionGroup.length) {

                        selectionGroup = selectionGroup[0];

                        var deselectSeats = [];
                        deselectSeats.push(selectionGroup.seatID);

                        angular.forEach(selectionGroup.seats, function (s) {
                            deselectSeats.push(s.seatID);
                        })

                        scope.selectionGroups = $.grep(scope.selectionGroups, function (sg) {
                            return sg.seatID != selectionGroup.seatID;
                        })

                        scope.chart.deselectObjects(deselectSeats);
                    }

                }

                if (!scope.selector.supressRemovingFromCartOnDeselection && scope.chart.selectedObjects.length == 0) {
                    scope.selector.options.holdTokenIsActive = false;
                }

            })

        }
        scope.selector.options.chartOptions.onObjectDeselected = scope.objectDeselected;

        scope.bestAvailableSelected = function (objects) {

            scope.clearNotEnoughSeatsInBestAvailableTimeout()

            //Nothing to do here yet since the seats are individually selected
            //which fires the objectSelected function

            scope.enabled = true;
        }
        scope.selector.options.chartOptions.onBestAvailableSelected = scope.bestAvailableSelected;

        scope.bestAvailableSelectionFailed = function () {

            scope.clearNotEnoughSeatsInBestAvailableTimeout();

            scope.enabled = true;

            scope.showMessage({
                autoHide: false,
                message: 'Unable to find seats matching the current criteria.',
                buttons: [
                    {
                        text: 'OK',
                        onClick: function () {
                            scope.closeMessage();
                        }
                    }
                ],
            });

        }
        scope.selector.options.chartOptions.onBestAvailableSelectionFailed = scope.bestAvailableSelectionFailed

        scope.onHoldFailed = function (objects) {

            scope.holdErrors += 1;

            if (!scope.ignoreHoldErrors) {
                scope.handleHoldFailure(objects);
            }
        }

        scope.selector.options.chartOptions.onHoldFailed = scope.onHoldFailed

        scope.onReleaseHoldFailed = function (objects) {

            if (!scope.ignoreReleaseHoldErrors) {
                scope.handleHoldFailure(objects);
            }

        }

        scope.selector.options.chartOptions.onReleaseHoldFailed = scope.onReleaseHoldFailed

        scope.handleHoldFailure = function (objects) {

            scope.showMessage({
                autoHide: false,
                message: 'An error has occurred. Please refresh the page and try again',
                buttons: [
                    {
                        text: 'Refresh Page',
                        onClick: function () {
                            scope.reloadPage();
                        }
                    }
                ],
            });

        }

        scope.clearNotEnoughSeatsInBestAvailableTimeout = function(){

            //Clear timeout here that checks to see if there aren't enough seats to satisfy the request
            if (scope.notEnoughSeatsInBestAvailable) {
                $timeout.cancel(scope.notEnoughSeatsInBestAvailable);
                scope.notEnoughSeatsInBestAvailable = null;
            }

        }

        scope.removeCartItem = function (seat) {

            scope.enabled = false;

            scope.removeSeatFromCartItem(seat.CartItemID);

            scope.selector.options.removeItemFromCart(seat.CartItemID, true).then(function (removedSeat) {
                scope.enabled = true;
                if (removedSeat != '') {
                    scope.selector.options.updateCartIndicator();
                    scope.enabled = true;

                }
                else {
                    scope.showMessage({
                        autoHide: false,
                        message: 'An error occurred while removing the seats from your cart. Please refresh the page and try again',
                        buttons: [
                            {
                                text: 'Refresh Page',
                                onClick: function () {
                                    scope.reloadPage();
                                }
                            }
                        ],
                    });
                }
            })
        }


        //CartItem object

        scope.removeSeatFromCartItem = function (cartItemID) {

            //remove item
            if (scope.selector.options.cartItem) {
                for (c = 0; c < scope.selector.options.cartItem.Categories.length; c++) {

                    var category = scope.selector.options.cartItem.Categories[c]

                    //Remove from seats collection

                    category.Seats = $.grep(category.Seats, function (s) {
                        return s.CartItemID != cartItemID;
                    })

                    //Remove from ga collection

                    category.GeneralAdmissions = $.grep(category.GeneralAdmissions, function (ga) {
                        return ga.CartItemID != cartItemID;
                    })

                }
            }

            scope.cleanupCartItem();

        }

        scope.cleanupCartItem = function () {

            if (scope.selector.options.cartItem) {
                scope.selector.options.cartItem.Categories = $.grep(scope.selector.options.cartItem.Categories, function (c) {
                    return c.Seats.length > 0 || c.GeneralAdmissions.length > 0;
                })

                if (scope.selector.options.cartItem.Categories.length == 0) {
                    scope.selector.options.cartItem = null;
                }
            }

        }

        //release seats

        scope.releaseSeat = function (seat) {

            scope.chart.deselectObjects([seat.id]);
            scope.selector.currentHolds = $.grep(scope.selector.currentHolds, function (r) {
                return r.id != seat.id
            });

        }

        scope.releaseExpiredSeat = function (seat) {

            scope.selector.expiredHolds = $.grep(scope.selector.expiredHolds, function (r) {
                return r.id != seat.id
            })

        }

        //retry holds

        scope.retryHolds = function (addToCart) {


            var seats = [];
            var expiredSeatsReference = [];

            for (r = 0; r < scope.selector.expiredHolds.length; r++) {

                var expiredHold = scope.selector.expiredHolds[r];

                var seat = {
                    label: expiredHold.id,
                }

                if (scope.selector.options.chartOptions.pricing) {
                    if (expiredHold.ticketTypeSelection) {
                        seat.ticketType = expiredHold.ticketTypeSelection.Name;
                    }
                     
                }

                seats.push(seat);
                expiredSeatsReference.push({
                    label: expiredHold.id,
                    ticketType: expiredHold.ticketTypeSelection && expiredHold.ticketTypeSelection.TicketTypeID ? expiredHold.ticketTypeSelection.TicketTypeID:null
                })

            }

            scope.seatsToHold = seats;
            scope.expiredSeatsReference = expiredSeatsReference;

            scope.enabled = false;

            scope.selector.expiredHolds = [];

            scope.holdErrors = 0;
            scope.ignoreHoldErrors = true;
            scope.chart.selectObjects(seats);

            $timeout(function () {

                scope.ignoreHoldErrors = false;
                scope.expiredSeatsReference = null;
                scope.reloadSelectionGroupsFromChartSelections();

                if (scope.holdErrors) {

                    scope.ignoreReleaseHoldErrors = true;
                    scope.chart.deselectObjects(scope.seatsToHold);

                    $timeout(function () {
                        scope.ignoreReleaseHoldErrors = false;
                        scope.enabled = true;
                        scope.resetChartAndToken();
                    }, 2000)

                    scope.showMessage({
                        autoHide: false,
                        message: 'The seats are no longer available.',
                        buttons: [
                            {
                                text: 'OK',
                                onClick: function () {
                                    scope.closeMessage();
                                }
                            }
                        ],
                    });
                }
                else {
                    scope.enabled = true;

                    if (addToCart)
                        scope.addTicketsToCart(true);
                }

            }, 2000)

        }

        scope.selector.options.retryHolds = scope.retryHolds;

        //functions

        scope.reloadPage = function () {
            location.reload();
        }

        scope.toggleShowMapOnBestSelected = function () {
            scope.showMapOnBestAvailable = !scope.showMapOnBestAvailable;
        }

        scope.getTicketTypeByID = function (ticketTypeID, categoryKey) {

            var returnTicketType = null;

            if (ticketTypeID) {
                for (c = 0; c < scope.selector.options.seatmap.Categories.length; c++) {
                    var category = scope.selector.options.seatmap.Categories[c];

                    if (category.Key == categoryKey) {
                        for (t = 0; t < category.TicketTypes.length; t++) {
                            var ticketType = category.TicketTypes[t];

                            if (ticketType.TicketTypeID == ticketTypeID) {
                                returnTicketType = ticketType;
                            }
                        }
                    }
                }
            }

            return returnTicketType;
        }

        scope.selector.getTicketTypeByID = scope.getTicketTypeByID

        scope.getCategoryKeyByName = function (categoryName) {

            var categoryKey = null;

            if (categoryName != "") {

                var category = $.grep(scope.selector.options.seatmap.Categories, function (r) {
                    return r.Name == categoryName
                })[0];

                if (category) {
                    categoryKey = category.Key;
                }
            }

            return categoryKey;
        }

        scope.getTicketTypeByName = function (ticketTypeName, categoryKey) {

            var returnTicketType = null;

            if (ticketTypeName) {
                for (c = 0; c < scope.selector.options.seatmap.Categories.length; c++) {
                    var category = scope.selector.options.seatmap.Categories[c];

                    if (category.Key == categoryKey) {
                        for (t = 0; t < category.TicketTypes.length; t++) {
                            var ticketType = category.TicketTypes[t];

                            if (ticketType.Name == ticketTypeName) {
                                returnTicketType = ticketType;
                            }
                        }
                    }
                }
            }

            return returnTicketType;
        }

        

        scope.getCategoryByKey = function (categoryKey) {

        }

        scope.pushSeatsIOSeatToHold = function (seatObject, ticketTypeID) {

            var extraConfig = seatObject.chart.config.extraConfig;
            var ticketTypeSelection = null;

            //Get overall status and hold type
            var objectsStatus = null;
            var objectsHoldType = null;
            var seasonTicketProductID = null;
            var hasBooked = false;

            var events = seatObject.chart.config.events ? seatObject.chart.config.events : [seatObject.chart.config.event];

            for (e = 0; e < events.length; e++) {

                var currentEvent = events[e];

                objectEventData = seatObject.dataPerEvent[currentEvent]

                if (objectEventData.extraData && objectEventData.extraData.holdTypeID && objectEventData.extraData.seasonTicketProductID && (objectEventData.extraData.seasonTicketProductID != extraConfig.productID)) {
                    seasonTicketProductID = objectEventData.extraData.seasonTicketProductID;
                }

                if (objectEventData.status == 'booked') {
                    hasBooked = true;
                }

                if (e == 0) {
                    objectsStatus = objectEventData.status;
                    if (objectEventData.extraData && objectEventData.extraData.holdTypeID) {
                        objectsHoldType = objectEventData.extraData.holdTypeID.toString();
                    }
                }
                else {
                    if (objectEventData.status != objectsStatus) {
                        objectsStatus = 'mixed';
                    }

                    if (objectEventData.extraData && objectEventData.extraData.holdTypeID) {
                        if (!objectsHoldType) {
                            objectsHoldType = objectEventData.extraData.holdTypeID.toString();
                        }
                        else if (objectEventData.extraData.holdTypeID != objectsHoldType) {
                            objectsHoldType = 'mixed';
                        }
                    }

                }

            }


            //Get Event Details

            var selectionEvents = [];

            for (e = 0; e < events.length; e++) {

                var currentEvent = events[e];

                var baseProduct = null;

                if (extraConfig.baseProducts) {
                    baseProduct = $.grep(extraConfig.baseProducts, function (bs) {
                        return bs.ID == currentEvent;
                    })

                    baseProduct = baseProduct.length ? baseProduct[0] : null;
                }

                objectEventData = seatObject.dataPerEvent[currentEvent]

                if (objectEventData) {

                    var selectionEvent = null;

                    if (baseProduct) {
                        //Season ticket product
                        selectionEvent = {
                            id: baseProduct.ID,
                            name: baseProduct.Name,
                            status: objectEventData.status,
                            extraData: objectEventData.extraData,
                        }

                    }
                    else {
                        //reserved seating product
                        selectionEvent = {
                            id: currentEvent,
                            status: objectEventData.status,
                            extraData: objectEventData.extraData,
                        }

                    }

                    selectionEvents.push(selectionEvent);
                }
            }

            if (ticketTypeID) {
                ticketTypeSelection = scope.getTicketTypeByID(ticketTypeID, seatObject.category.key);
            }

            var hold = {
                id: seatObject.label,
                labelID: seatObject.label,
                label: seatObject.labels.own,
                parentType: seatObject.parent ? seatObject.parent.type : null,
                parent: seatObject.labels.parent ? seatObject.labels.parent : null,
                section: seatObject.labels.section,
                categoryKey: seatObject.category.key,
                objectType: seatObject.objectType,
                ticketTypeSelection: ticketTypeSelection,
                holdTypeID: seatObject.extraData ? seatObject.extraData.holdTypeID : null,
                seasonTicketProductID: seasonTicketProductID,
                status: objectsStatus,
                holdType: objectsHoldType,
                events: selectionEvents
            };

            if (seatObject.objectType == "GeneralAdmissionArea") {

                hold.gaSeatID = ($.grep(scope.selector.currentHolds, function (h) {
                    return h.labelID == seatObject.label && h.categoryKey == seatObject.category.key
                }).length) + 1;
            }

            scope.selector.currentHolds.push(hold);           

            
        }

        scope.removeGASeatSelectionFromGAArea = function (gaObject) {
            var holdGAObject;

            if (scope.selector.holdMode == 'pick' && scope.selector.options.pricing) {
                var ticketType = scope.getSelectedGAObjectTicketType(gaObject);
                holdGAObject = scope.getCurrentHoldGAObjectByIDAndTicketTypeName(gaObject.id, ticketType.Name);
            }

            if (scope.selector.holdMode == 'bestAvailable' || !scope.selector.options.pricing) {
                holdGAObject = scope.getCurrentHoldGAObjectByGaAreaIDBySeatID(gaObject.id, scope.getGAObjectSeatID(gaObject));
            }

            var index = scope.selector.currentHolds.indexOf(holdGAObject);

            if (index > -1) {
                scope.selector.currentHolds.splice(index, 1);
                scope.updateGASeatSelectionInGAArea(gaObject);
            }

            return holdGAObject;
        }

        scope.getGAAreaByGAObjectID = function (gaObjectID) {

            return $.grep(scope.selector.gaAreasHavingGAHolds, function (ga) {
                return ga.id == gaObjectID
            })[0];
        }

        scope.getGAObjectSeatID = function (gaObject) {

            var gaAreaHolds = $.grep(scope.selector.currentHolds, function (r) {
                return r.id == gaObject.id && r.categoryKey == gaObject.category.key && r.objectType == 'GeneralAdmissionArea'
            });

            var selectedGAHold = gaAreaHolds[gaAreaHolds.length - 1];

            gaAreaHolds.splice(gaAreaHolds.indexOf(selectedGAHold), 1);

            return selectedGAHold.gaSeatID;
        }

        scope.getCurrentHoldGAObjectByGaAreaIDBySeatID = function (gaAreaID, gaSeatID) {

            return $.grep(scope.selector.currentHolds, function (r) {
                return r.id == gaAreaID && r.gaSeatID == gaSeatID
            })[0];
        }

        scope.getCurrentHoldGAObjectByIDAndTicketTypeName = function (gaObjectID, ticketTypeName) {

            return $.grep(scope.selector.currentHolds, function (r) {
                return r.id == gaObjectID && r.objectType == 'GeneralAdmissionArea' && r.ticketTypeSelection.Name == ticketTypeName;
            })[0];
        }

        scope.updateGASeatSelectionInGAArea = function (gaObject) {

            var gaArea = scope.getGAAreaByGAObjectID(gaObject.id);            

            if (gaArea) {
                gaArea.selectionPerTicketType = angular.fromJson(angular.toJson(gaObject.selectionPerTicketType));

            } else {
                scope.selector.gaAreasHavingGAHolds.push({
                    id: gaObject.label,
                    labelID: gaObject.label,
                    label: gaObject.labels.own,
                    parent: gaObject.labels.parent ? gaObject.labels.parent : null,
                    section: gaObject.labels.section,
                    categoryKey: gaObject.category.key,
                    objectType: gaObject.objectType,
                    selectionPerTicketType: angular.fromJson(angular.toJson(gaObject.selectionPerTicketType))
                })
            }
        }

        scope.getSelectedGAObjectTicketType = function (gaObject) {

            var gaArea = scope.getGAAreaByGAObjectID(gaObject.id);

            for (ticketTypeName in gaArea.selectionPerTicketType) {
                if (gaObject.selectionPerTicketType && gaObject.selectionPerTicketType.hasOwnProperty(ticketTypeName) && gaObject.selectionPerTicketType[ticketTypeName] != gaArea.selectionPerTicketType[ticketTypeName]) {
                    return scope.getTicketTypeByName(ticketTypeName, gaArea.categoryKey);
                }
            }

            return null;
        }

        scope.timer = $interval(function () {
            scope.processTimer();
        }, 500)

        scope.processTimer = function () {
            if (scope.selector.options.holdToken && scope.selector.options.chartOptions.extraConfig.selectionMode != "Onhold") {

                var previousTime = scope.selector.timeLeft;

                scope.selector.timeLeft = scope.selector.options.holdToken.ExpiresInSeconds - 0 - parseInt((new Date().getTime() - scope.selector.startTime) / 1000);

                if (scope.selector.timeLeft < 1) {
                    scope.selector.isExpired = true;
                    scope.selector.timeLeft = 0;
                    if (previousTime != 0) {
                        scope.timerExpired();
                    }
                }
                else {
                    scope.selector.isExpired = false;
                }
            }
        }

        scope.timerExpired = function () {

            var currentHolds = angular.fromJson(angular.toJson(scope.selector.currentHolds));

            scope.chart.clearSelection();

            scope.enabled = false;

            var resetChartInterval = $interval(function () {

                if (scope.chart.selectedObjects.length == 0) {

                    scope.enabled = false;

                    for (r = 0; r < currentHolds.length; r++) {
                        scope.selector.expiredHolds.push(currentHolds[r])
                    }

                    $interval.cancel(resetChartInterval);

                    scope.selector.options.cartItem = null;

                     scope.resetChartAndToken();
                }

            }, 500)
        }

        scope.clearSelectionCompleteWithIgnoreErrors = function () {
            $timeout(function () {
                //For some reason this function no longer runs after the clear is complete so
                //we are adding some time before clearing the ignore flag
                scope.ignoreReleaseHoldErrors = undefined;
            },3000)
            
        }


        scope.clearHolds = function (ignoreReleaseHoldErrors) {

            scope.selector.options.cartItem = null
            scope.selector.currentHolds = [];
            scope.selector.expiredHolds = [];

            if (scope.chart) {

                if (ignoreReleaseHoldErrors) {
                    scope.ignoreReleaseHoldErrors = true;

                    $timeout(function () {
                        scope.chart.clearSelection(null, scope.clearSelectionCompleteWithIgnoreErrors);
                    }, 50)
                }
                else {
                    scope.chart.clearSelection();
                }
                

                scope.enabled = false;
                scope.showMapOnBestAvailable = false;

                var clearHoldsInterval = $interval(function () {

                    if (scope.chart.selectedObjects.length == 0) {

                        $interval.cancel(clearHoldsInterval);

                        if (!scope.selector.options.cartItem) {
                            scope.resetChartAndToken();
                        }

                    }

                }, 500)
            }

            scope.clearGASelection();
        }

        scope.selector.clearHolds = scope.clearHolds;

        scope.resetChartAndToken = function () {
            reservedSeatingService.generateHoldToken(scope.selector.options.productID, scope.selector.holdMode).then(function (expiration) {
                if (expiration.ExceptionType) {

                    var exceptionMessage = 'An error has occurred. Please refresh the page and try again';

                    if (expiration.ExceptionType.search('BLLException') && expiration.ExceptionMessage) {
                        exceptionMessage = expiration.ExceptionMessage;
                    }

                    scope.showMessage({
                        autoHide: false,
                        message: exceptionMessage,
                        buttons: [
                            {
                                text: 'Refresh Page',
                                onClick: function () {
                                    scope.reloadPage();
                                }
                            }
                        ],
                    });
                }
                else {
                    scope.selector.options.holdToken = expiration
                    scope.selector.options.chartOptions.holdToken = expiration.Token;
                    scope.selector.startTime = new Date();
                    scope.processTimer();
                    scope.selector.isIdle = true;
                    scope.enabled = true;
                    scope.selector.options.holdTokenIsActive = false;
                }

            })
        }

        scope.setHoldMode = function (mode, pickMode) {

            if (mode != scope.selector.holdMode || (scope.selector.options.chartOptions.extraConfig && scope.selector.options.chartOptions.extraConfig.selectionMode != pickMode)) {

                if (scope.selector.currentHolds.length) {

                    var message = 'Are you sure you want to remove your current seat selection?'

                    scope.showMessage({
                        autoHide: false,
                        message: message,
                        buttons: [
                            {
                                text: 'Continue',
                                onClick: function () {
                                    scope.closeMessage();
                                    scope.selector.options.holdTokenIsActive = false;
                                    scope.processModeChangeWithActiveItems(mode);
                                    scope.setSelectionMode(pickMode);
                                }
                            },
                            {
                                text: 'Cancel',
                                onClick: function () {
                                    scope.closeMessage();
                                }
                            }
                        ],
                    });

                }
                else {
                    scope.selector.expiredHolds = [];
                    scope.setSelectionModeOptions(mode)
                    reservedSeatingService.setSelectionMode(scope.selector.options.productID, mode);
                    scope.showMapOnBestAvailable = false;
                    scope.setSelectionMode(pickMode);
                }


            }

        }

        scope.setSelectionMode = function (selectionMode) {
            scope.selector.options.chartOptions.extraConfig.selectionMode = selectionMode;
            scope.clearReservations();
        }

        scope.clearReservations = function () {
            scope.clearHolds(true);
            scope.clearGASelection();

        }

        scope.clearGASelection = function () {

            if (scope.selector && scope.selector.options.chartOptions.extraConfig && scope.selector.options.chartOptions.extraConfig.generalAdmissions) {
                angular.forEach(scope.selector.options.chartOptions.extraConfig.generalAdmissions, function (ga) {
                    angular.forEach(ga.Holds, function (h) {
                        h.PriceTypes = [];
                    });
                });
            }
        }

        scope.getPriceTypeIDByName = function(categoryKey, priceTypeName){

            var priceTypeID = null;

            if (scope.selector && scope.selector.options && scope.selector.options.seatmap && scope.selector.options.seatmap.Categories) {

                var category = $.grep(scope.selector.options.seatmap.Categories, function (c) {
                    return c.Key == categoryKey;
                })

                category = category.length ? category[0] : null;

                if (category) {

                    if (category.TicketTypes) {

                        var priceType = $.grep(category.TicketTypes, function (pt) {
                            return pt.Name == priceTypeName;
                        })

                        priceTypeID = priceType.length ? priceType[0].TicketTypeID : null;

                    }

                    
                }
            }

            return priceTypeID;

        }

        scope.setTicketTypeSelectionForAllInCategory = function (category, ticketTypeID) {

            if (ticketTypeID) {
                ticketTypeSelection = scope.getTicketTypeByID(ticketTypeID, category.Key);

                angular.forEach(category.seats,function(s){
                    s.ticketTypeSelection = ticketTypeSelection;
                })

            }

        }

        scope.setSelectionModeOptions = function (mode) {

            //need to remove pricing when in best available mode
            //and add it if the pricing option exists in select mode
            scope.selector.holdMode = mode;

            if (mode == 'pick') {
                if (scope.selector.options.pricing) {
                    scope.selector.options.chartOptions.pricing = scope.selector.options.pricing;
                    scope.seatmapPricingEnabled = true
                }
                else {
                    scope.seatmapPricingEnabled = false
                }
            }
            else {
                scope.selector.options.chartOptions.pricing = undefined;
                scope.seatmapPricingEnabled = false
            }

        }

        scope.processModeChangeWithActiveItems = function (mode) {

            scope.enabled = false;

            scope.chart.clearSelection();

            var clearSelectionsInterval = $interval(function () {

                if (scope.chart.selectedObjects.length == 0) {

                    $interval.cancel(clearSelectionsInterval);

                    //Give the renderer time to work.
                    //If this isn't in place then the seats are still shown as selected when the chart is re-drawn
                    $timeout(function () {

                        scope.selector.currentHolds = [];
                        scope.selector.expiredHolds = [];

                        scope.setSelectionModeOptions(mode);

                        reservedSeatingService.setSelectionMode(scope.selector.options.productID, mode);

                        if (scope.selector.options.cartItem) {
                            scope.selector.options.removeFromCart(scope.selector.options.productID).then(function (success) {

                                scope.enabled = true;

                                if (success == true) {
                                    scope.selector.options.cartItem = null;
                                    scope.selector.options.updateCartIndicator();
                                }
                                else {
                                    scope.showMessage({
                                        autoHide: false,
                                        message: 'An error has occurred, please refresh this page and try again',
                                        buttons: [
                                            {
                                                text: 'Refresh Page',
                                                onClick: function () {
                                                    scope.reloadPage();
                                                }
                                            }
                                        ],
                                    });
                                }
                            })
                        }
                        else {
                            scope.enabled = true;
                        }
                    },1000)

                    

                    

                }

            }, 500)

        }

        scope.ticketTypesAreValid = function () {
            for (r = 0; r < scope.selector.currentHolds.length; r++) {
                if (!scope.selector.currentHolds[r].ticketTypeSelection) {
                    return false
                }
            }

            return true
        }

        scope.groupsAreValid = function () {

            if (scope.selector.options.enforceGroupSelection) {

                var seatsInGroups = [];

                var isValid = true;

                angular.forEach(scope.selectionGroups, function (sg) {
                    seatsInGroups.push(sg.seatID);
                    angular.forEach(sg.seats, function (s) {
                        seatsInGroups.push(s.seatID);
                    })
                })

                angular.forEach(seatsInGroups, function (spd) {
                    if ($.grep(scope.selector.currentHolds, function (h) {
                        return h.labelID == spd;
                    }).length == 0) {
                        isValid = false;
                        return;
                    }
                })

                return isValid

            }
            else {
                return true;
            }

        }


        scope.addTicketsToCart = function (redirect) {

            if (!scope.groupsAreValid()) {

                scope.showMessage({
                    autoHide: false,
                    message: 'All seats within a group must be selected.',
                    buttons: [
                        {
                            text: 'OK',
                            onClick: function () {
                                scope.closeMessage();
                            }
                        }
                    ],
                });

                return;
            }
            
            if (scope.chart.selectionIsValid != undefined && !scope.chart.selectionIsValid && scope.selector.holdMode == 'pick') {

                scope.showMessage({
                    autoHide: false,
                    message: 'Please leave no empty seats.',
                    buttons: [
                        {
                            text: 'OK',
                            onClick: function () {
                                scope.closeMessage();
                            }
                        }
                    ],
                });

                return;
            }

            if (!scope.ticketTypesAreValid()) {

                scope.showMessage({
                    autoHide: false,
                    message: 'Please select a type for each seat before adding to the cart.',
                    buttons: [
                        {
                            text: 'OK',
                            onClick: function () {
                                scope.closeMessage();
                            }
                        }
                    ],
                });

            }
            else {

                if (scope.selector.options.environment == 'Kiosk') {
                    reservedSeatingService.startHoldToken(scope.selector.options.productID, scope.selector.options.environment + ',OrderPlacement').then(function (expiration) {

                        scope.holdTokenIsActivating = false;

                        if (expiration.ExceptionType) {

                            var exceptionMessage = 'An error has occurred. Please refresh the page and try again';

                            if (expiration.ExceptionType.search('BLLException') && expiration.ExceptionMessage) {
                                exceptionMessage = expiration.ExceptionMessage;
                            }

                            $timeout(function () {
                                scope.showMessage({
                                    autoHide: false,
                                    message: exceptionMessage,
                                    buttons: [
                                        {
                                            text: 'Refresh Page',
                                            onClick: function () {
                                                $scope.reloadPage();
                                            }
                                        }
                                    ],
                                });

                            }, 500)
                        }
                        else {
                            scope.selector.options.holdToken.ExpiresInSeconds = expiration - 5; //take a little time off to account for response time
                            scope.selector.startTime = new Date();
                            scope.selector.isIdle = false;
                            scope.suppressLoadingMask = false;
                            scope.processTimer();
                            scope.selector.options.holdTokenIsActive = true;

                            scope.addHeldTicketsToCart(redirect);

                        }


                    })
                }
                else {

                    scope.addHeldTicketsToCart(redirect)

                }

            }

        }

        scope.addHeldTicketsToCart = function (redirect) {
            var hold = {
                productID: scope.selector.options.productID,
                itemID: scope.selector.options.itemID,
                isUpsell: scope.selector.options.isUpsell,
                holdToken: scope.selector.options.holdToken,
                holdMode: scope.selector.holdMode,
                selectionMode: scope.selector.options.chartOptions.extraConfig.selectionMode,
                categories: []
            }

            var displayHolds = scope.selector.displayHolds();

            for (c = 0; c < displayHolds.length; c++) {
                var displayCategory = displayHolds[c];

                var category = {
                    categoryID: displayCategory.ID,
                    name: displayCategory.Name,
                    seats: []
                }

                for (s = 0; s < displayCategory.seats.length; s++) {

                    var displaySeat = displayCategory.seats[s];

                    category.seats.push({
                        seatID: displaySeat.id,
                        label: displaySeat.labelID,
                        ticketTypeID: displaySeat.ticketTypeSelection.TicketTypeID,
                        seatLabel: displaySeat.label,
                        seatParentType: displaySeat.parentType,
                        seatParentLabel: displaySeat.parent,
                        seatSectionLabel: displaySeat.section,
                        objectType: displaySeat.objectType,
                        price: displaySeat.ticketTypeSelection.Price,
                        displayPrice: displaySeat.ticketTypeSelection.DisplayPrice,
                        holdTypeID: displaySeat.holdTypeID,
                        seasonTicketProductID: displaySeat.seasonTicketProductID,
                        status: displaySeat.status,
                        holdType: displaySeat.holdType,
                        events: displaySeat.events
                    })
                }

                hold.categories.push(category);
            }

            scope.enabled = false;

            if (scope.selector.options.environment == 'Online') {
                hold.fbEventID = guid();
            }

            scope.selector.options.addHoldToCart(hold).then(function (data) {
                if (data.IsSuccess == true) {
                    if (scope.selector.options.environment == 'Online' && FB_CONFIG) {
                        fbq('track', 'AddToCart', null, { eventID: hold.fbEventID });
                    }

                    if (redirect) {
                        scope.enabled = true;
                        scope.selector.options.cartUpdatedRedirect(data.RedirectURL);
                    }
                    else {
                        scope.selector.options.updateCartIndicator();
                        scope.enabled = true;
                    }
                }
                else {
                    scope.enabled = true;

                    var message = 'An error occurred while adding the seats to your cart. Please refresh the page and try again'

                    scope.showMessage({
                        autoHide: false,
                        message: message,
                        buttons: [
                            {
                                text: 'Refresh Page',
                                onClick: function () {
                                    scope.reloadPage();
                                }
                            }
                        ],
                    });
                }

            });
        }

        scope.$watch('selector.options.initialHoldMode', function (newValue, oldValue) {

            if (scope.selector.options.productID) {

                if (scope.selector.options.initialHoldMode) {

                    scope.setHoldMode(scope.selector.options.initialHoldMode, scope.selector.options.chartOptions.extraConfig.selectionMode)

                }
                else {
                    scope.setHoldMode('bestAvailable')
                }
            }
            

        }, true);

        scope.$watch('selector.holdMode', function (newValue, oldValue) {

            if (scope.selector.holdMode == 'bestAvailable') {
                scope.selector.options.chartOptions.mode = 'static';
            }
            else {
                scope.selector.options.chartOptions.mode = 'normal';
            }

        }, true);

        scope.handlePreloadFoundSeat = function (seatObject) {

            if (seatObject.objectType != "GeneralAdmissionArea") {

                scope.$apply(function () {

                    if (!$.grep(scope.selectionGroups, function (sg) {
                        return sg.seatID == seatObject.id || $.grep(sg.seats, function (s) { return s.seatID == seatObject.id }).length;
                    }).length) {
                        scope.addSelectionGroup(seatObject, false);
                    }

                })

            }


        }

        scope.reloadSelectionGroupsFromChartSelections = function () {

            scope.selectionGroups = [];

            var selections = scope.chart.selectedObjects;

            if (selections.length) {
                scope.preLoadGroups = selections;

                scope.chart.findObject(selections[0], scope.handlePreloadFoundSeat);

                scope.preLoadGroups.shift()

                $timeout(function () {

                    scope.preLoadGroupsInterval = $interval(function () {

                        for (si = scope.preLoadGroups.length - 1; si >= 0; si--) {

                            angular.forEach(scope.selectionGroups, function (sg) {
                                if (sg.seatID == scope.preLoadGroups[si]) {
                                    scope.preLoadGroups.pop();
                                    return;

                                    angular.forEach(sg.seats, function (s) {
                                        if(s.seatID == scope.preLoadGroups[si]){
                                            scope.preLoadGroups.pop();
                                            return;
                                        }
                                    })
                                }
                            })

                        }

                        if (scope.preLoadGroups.length) {

                            scope.chart.findObject(scope.preLoadGroups[0], scope.handlePreloadFoundSeat);

                            scope.preLoadGroups.shift()

                        }
                        else {
                            $interval.cancel(scope.preLoadGroupsInterval);
                        }


                    }, 500)

                }, 1000)


            }

        }

        scope.$watch('selector.chartIsLoaded', function (newValue, oldValue) {

            if (scope.selector.chartIsLoaded == true) {

                if (scope.selector.options.enforceGroupSelection) {

                    //see if we need to populate the selection groups
                    //When the chart populates already selected seats on load
                    //the find function doesn't work until the chart is fully loaded
                    //so we have to re-populate here

                    scope.reloadSelectionGroupsFromChartSelections();

                }

                scope.suppressLoadingMask = false;
            }

        }, true);

        scope.$watch('selector.currentHolds', function (newValue, oldValue) {

            var seatsAreTogether = true;

            if (scope.selector.currentHolds) {

                if (scope.selector.currentHolds.length > 1) {

                    var testSection = scope.selector.currentHolds[0].section;
                    var testParent = scope.selector.currentHolds[0].parent;
                    var testSeats = [];

                    if (scope.selector.holdMode == 'bestAvailable') {
                        for (r = 0; r < newValue.length; r++) {
                            var newHold = newValue[r];

                            if (newHold.objectType == "GeneralAdmissionArea") {
                                var gaArea = scope.getGAAreaByGAObjectID(newHold.id);

                                var oldHold = $.grep(oldValue, function (h) {
                                    return h.gaSeatID == newHold.gaSeatID
                                })[0];

                                if (gaArea && gaArea.selectionPerTicketType && newHold != oldHold) {
                                    if (newHold.ticketTypeSelection && !gaArea.selectionPerTicketType.hasOwnProperty(newHold.ticketTypeSelection.Name)) {
                                        gaArea.selectionPerTicketType[newHold.ticketTypeSelection.Name] = 0;
                                    }

                                    if (oldHold && oldHold.ticketTypeSelection == null && newHold.ticketTypeSelection != null) {
                                        gaArea.selectionPerTicketType[newHold.ticketTypeSelection.Name] = gaArea.selectionPerTicketType[newHold.ticketTypeSelection.Name] + 1;
                                    }

                                    if (oldHold && oldHold.ticketTypeSelection != null && newHold.ticketTypeSelection == null) {
                                        gaArea.selectionPerTicketType[newHold.ticketTypeSelection.Name] = gaArea.selectionPerTicketType[newHold.ticketTypeSelection.Name] - 1;
                                    }

                                    if (oldHold && oldHold.ticketTypeSelection != null && newHold.ticketTypeSelection != null && oldHold.ticketTypeSelection.TicketTypeID != newHold.ticketTypeSelection.TicketTypeID) {
                                        gaArea.selectionPerTicketType[oldHold.ticketTypeSelection.Name] = gaArea.selectionPerTicketType[oldHold.ticketTypeSelection.Name] - 1;
                                        gaArea.selectionPerTicketType[newHold.ticketTypeSelection.Name] = gaArea.selectionPerTicketType[newHold.ticketTypeSelection.Name] + 1;
                                    }
                                }
                            }
                        }
                    }

                    for (r = 0; r < scope.selector.currentHolds.length; r++) {
                        var hold = scope.selector.currentHolds[r];

                        if (!hold.label || (hold.label && !IsNumeric(hold.label))) {
                            //If there is no seat label then just return true because we can't determine if they are together
                            scope.seatsAreTogether = true
                            return true;
                        }

                        if (hold.section != testSection || hold.parent != testParent) {
                            seatsAreTogether = false;
                        }
                        else {
                            testSeats.push(parseInt(hold.label));
                        }

                    }

                    if(seatsAreTogether){
                        var max = Math.max.apply(null,testSeats);
                        var min = Math.min.apply(null,testSeats);

                        for (t = min; t <= max; t++) {
                            if (testSeats.indexOf(t) < 0) {
                                seatsAreTogether = false;
                            }
                        }
                    }

                }

            }

            scope.seatsAreTogether = seatsAreTogether;

        }, true);

        scope.$watch('selector.options.seatmap', function (newValue, oldValue) {

            if (scope.selector.options.seatmap && (!scope.initialized || !oldValue || (oldValue && newValue.ID != oldValue.ID))) {

                scope.initialized = true;

                scope.selector.currentHolds = [];
                scope.selector.expiredHolds = [];

                scope.selector.isExpired = true;
                scope.selector.timeLeft = 0;

                scope.selector.startTime = new Date();

                //set qty dropdown options
                scope.qtyOptions = [];
                for (q = 1; q <= scope.selector.options.seatmap.OrderLimit; q++) {
                    scope.qtyOptions.push(q);
                }

                scope.qtySelection = 1;

                scope.bestAvailableSelectionCategories = [];

                //set category dropdown options
                scope.bestAvailableSelectionCategories.push({
                    id: 0,
                    name: 'Select...'
                })

                scope.categorySelection = scope.bestAvailableSelectionCategories[0].name;

                for (c = 0; c < scope.selector.options.seatmap.Categories.length; c++) {
                    var category = scope.selector.options.seatmap.Categories[c]
                    if (category.TicketTypes.length > 0) {
                        scope.bestAvailableSelectionCategories.push({
                            id: category.ID,
                            name: category.Name,
                            displayPriceRange: scope.selector.options.seatmap.ProductType == 5 ? category.DisplayPriceRange : ''
                        })
                    }
                    
                }
            }

        }, true);

        scope.$watch('[selector.holdMode, selector.currentHolds, selector.expiredHolds, qtySelection, categorySelection, seatsToHold, showMapOnBestAvailable]', function (newValue, oldValue) {

            var valuesChanged = false;

            if (newValue[0] != oldValue[0]) {
                valuesChanged = true;

            } else if ((newValue[1] != undefined && oldValue[1] != undefined) && newValue[1].length != oldValue[1].length) {
                valuesChanged = true;

            } else if ((newValue[2] != undefined && oldValue[2] != undefined) && newValue[2].length != oldValue[2].length) {
                valuesChanged = true;

            } else if (newValue[3] != oldValue[3]) {
                valuesChanged = true;

            } else if (newValue[4] != oldValue[4]) {
                valuesChanged = true;

            } else if ((newValue[5] != undefined && oldValue[5] != undefined) && newValue[5].length != oldValue[5].length) {
                valuesChanged = true;

            } else if (newValue[6] != oldValue[6]) {
                valuesChanged = true;
            }

            if (valuesChanged) {
                scope.selector.activityTracker = new Date().getTime();
            }
            
        }, true);

    };

    return {
        restrict: 'E',
        scope: {
            selector: '=',
            enabled: '=',
            suppressLoadingMask: '=',
            showMessage: '=',
            closeMessage: '=',
        },
        templateUrl: SITEBASEURL + 'core/content/angular/seatmap/seatSelector.html?sv=' + SITEVERSION,
        link: link

    }

    //selector
        //options
            //chartOptions
            //cartItem
            //productID
            //holdToken
            //seatmap
            //seatSelectionTypeChangeAllowed
            //initialHoldMode
            //itemID
            //updateCartIndicator
            //addHoldToCart
            //removeFromCart
            //removeItemFromCart
            //environment
            //disableTicketLimit
            //pricing
            //holdTokenIsActive
    //chartIsLoaded
    //isIdle
    //isExpired
    //timeLeft
    //startTime
    //currentHolds
    //expiredHolds
    //displayHolds

}]);

/*********************************************/
/*Directives*/
/*********************************************/

saffire.angular.app.directive('seatConfiguration', ['$interval', function ($interval) {

    function link(scope, element, attrs) {

        scope.populatePriceTypeDescriptionIfAny = function () {
            var ticketType = scope.seat.ticketTypeSelection;

            if (ticketType) {
                if (ticketType && ticketType.BaseProducts && ticketType.BaseProducts.length > 0 && ticketType.BaseProducts[0].Description) {
                    ticketType.hasPriceTypeDescription = true;
                    ticketType.priceTypeDescription = ticketType.BaseProducts[0].Description;
                } else {
                    ticketType.hasPriceTypeDescription = false;
                }
            }
        }

        if (!scope.seat.ticketTypeSelection && (scope.category && scope.category.TicketTypes.length == 1)) {
            scope.seat.ticketTypeSelection = scope.category.TicketTypes[0];
        }

        var objectLabels = [];

        if (scope.seat.section && (scope.seat.section.toLowerCase() != scope.category.Name.toLowerCase())) {
            objectLabels.push('<span><strong>Section:</strong> ' + scope.seat.section + '</span>');
        }

        if (scope.seat.parent) {
            objectLabels.push('<span><strong>Row:</strong> ' + scope.seat.parent + '</span>');
        }

        if (scope.seat.label) {
            var objectTypeLabel = "";

            if (scope.seat.objectType != 'GeneralAdmissionArea') {
                objectTypeLabel = '<span><strong>' + scope.seat.objectType + ':</strong>'
            }

            objectLabels.push('<span>' + objectTypeLabel + scope.seat.label + '</span>');
        }

        if (scope.seat.ticketTypeSelection && scope.seat.ticketTypeSelection.DisplayPrice % 1 != 0) { //Ensuring the price is shown with 2 decimal places.
            var decimal = scope.seat.ticketTypeSelection.Label.split('.')[1].trim()
            if (decimal.length == 1) {
                scope.seat.ticketTypeSelection.Label = scope.seat.ticketTypeSelection.Label.concat('0');
            }
        }

        scope.objectLabel = objectLabels.join(" ");

        scope.populatePriceTypeDescriptionIfAny();
    };

    return {
        restrict: 'E',
        scope: {
            seat: '=',
            category: '=',
            isExpired: '=',
            allowRemove: '=',
            seatmapPricingEnabled: '=',
            releaseSeat: '&?',
            setForAll: '&',
            showSetForAll: '='
        },
        templateUrl: SITEBASEURL + 'core/content/angular/seatmap/seatconfiguration.html?sv=' + SITEVERSION,
        link: link

    }

}]);

