/* Minification failed. Returning unminified contents.
(4615,8-9): run-time error JS1010: Expected identifier: .
(4615,8-9): run-time error JS1195: Expected expression: .
(4629,8-9): run-time error JS1010: Expected identifier: .
(4629,8-9): run-time error JS1195: Expected expression: .
(4668,8-9): run-time error JS1010: Expected identifier: .
(4668,8-9): run-time error JS1195: Expected expression: .
(4738,8-9): run-time error JS1010: Expected identifier: .
(4738,8-9): run-time error JS1195: Expected expression: .
(4778,8-9): run-time error JS1010: Expected identifier: .
(4778,8-9): run-time error JS1195: Expected expression: .
(5447,8-9): run-time error JS1010: Expected identifier: .
(5447,8-9): run-time error JS1195: Expected expression: .
(5457,8-9): run-time error JS1010: Expected identifier: .
(5457,8-9): run-time error JS1195: Expected expression: .
(5715,8-9): run-time error JS1010: Expected identifier: .
(5715,8-9): run-time error JS1195: Expected expression: .
(5841,8-9): run-time error JS1010: Expected identifier: .
(5841,8-9): run-time error JS1195: Expected expression: .
(5875,8-9): run-time error JS1010: Expected identifier: .
(5875,8-9): run-time error JS1195: Expected expression: .
(5930,8-9): run-time error JS1010: Expected identifier: .
(5930,8-9): run-time error JS1195: Expected expression: .
(6055,8-9): run-time error JS1010: Expected identifier: .
(6055,8-9): run-time error JS1195: Expected expression: .
(6118,8-9): run-time error JS1010: Expected identifier: .
(6118,8-9): run-time error JS1195: Expected expression: .
(6187,9-10): run-time error JS1010: Expected identifier: .
(6187,9-10): run-time error JS1195: Expected expression: .
(6199,8-9): run-time error JS1010: Expected identifier: .
(6199,8-9): run-time error JS1195: Expected expression: .
(6220,8-9): run-time error JS1010: Expected identifier: .
(6220,8-9): run-time error JS1195: Expected expression: .
(6232,8-9): run-time error JS1010: Expected identifier: .
(6232,8-9): run-time error JS1195: Expected expression: .
(6245,8-9): run-time error JS1010: Expected identifier: .
(6245,8-9): run-time error JS1195: Expected expression: .
(6277,8-9): run-time error JS1010: Expected identifier: .
(6277,8-9): run-time error JS1195: Expected expression: .
(6299,8-9): run-time error JS1010: Expected identifier: .
(6299,8-9): run-time error JS1195: Expected expression: .
(6312,8-9): run-time error JS1010: Expected identifier: .
(6312,8-9): run-time error JS1195: Expected expression: .
(6328,8-9): run-time error JS1010: Expected identifier: .
(6328,8-9): run-time error JS1195: Expected expression: .
(6563,8-9): run-time error JS1010: Expected identifier: .
(6563,8-9): run-time error JS1195: Expected expression: .
(7223,8-9): run-time error JS1010: Expected identifier: .
(7223,8-9): run-time error JS1195: Expected expression: .
(8273,11-12): run-time error JS1010: Expected identifier: .
(8273,11-12): run-time error JS1195: Expected expression: .
(8997,8-9): run-time error JS1010: Expected identifier: .
(8997,8-9): run-time error JS1195: Expected expression: .
(9106,8-9): run-time error JS1010: Expected identifier: .
(9106,8-9): run-time error JS1195: Expected expression: .
(9157,8-9): run-time error JS1010: Expected identifier: .
(9157,8-9): run-time error JS1195: Expected expression: .
(9370,10-11): run-time error JS1010: Expected identifier: =
(9370,10-11): run-time error JS1195: Expected expression: =
(9372,10-11): run-time error JS1010: Expected identifier: =
(9372,10-11): run-time error JS1195: Expected expression: =
(9375,8-9): run-time error JS1010: Expected identifier: .
(9375,8-9): run-time error JS1195: Expected expression: .
(9448,8-9): run-time error JS1010: Expected identifier: .
(9448,8-9): run-time error JS1195: Expected expression: .
(9496,8-9): run-time error JS1010: Expected identifier: .
(9496,8-9): run-time error JS1195: Expected expression: .
(9549,10-11): run-time error JS1010: Expected identifier: =
(9549,10-11): run-time error JS1195: Expected expression: =
(9551,10-11): run-time error JS1010: Expected identifier: =
(9551,10-11): run-time error JS1195: Expected expression: =
(9554,8-9): run-time error JS1010: Expected identifier: .
(9554,8-9): run-time error JS1195: Expected expression: .
(9576,8-9): run-time error JS1010: Expected identifier: .
(9576,8-9): run-time error JS1195: Expected expression: .
(9606,8-9): run-time error JS1010: Expected identifier: .
(9606,8-9): run-time error JS1195: Expected expression: .
(9651,10-11): run-time error JS1010: Expected identifier: =
(9651,10-11): run-time error JS1195: Expected expression: =
(9653,10-11): run-time error JS1010: Expected identifier: =
(9653,10-11): run-time error JS1195: Expected expression: =
(9656,8-9): run-time error JS1010: Expected identifier: .
(9656,8-9): run-time error JS1195: Expected expression: .
(11688,10-11): run-time error JS1010: Expected identifier: =
(11688,10-11): run-time error JS1195: Expected expression: =
(11690,10-11): run-time error JS1010: Expected identifier: =
(11690,10-11): run-time error JS1195: Expected expression: =
(11693,8-9): run-time error JS1010: Expected identifier: .
(11693,8-9): run-time error JS1195: Expected expression: .
(11802,10-11): run-time error JS1010: Expected identifier: =
(11802,10-11): run-time error JS1195: Expected expression: =
(11804,10-11): run-time error JS1010: Expected identifier: =
(11804,10-11): run-time error JS1195: Expected expression: =
(11807,8-9): run-time error JS1010: Expected identifier: .
(11807,8-9): run-time error JS1195: Expected expression: .
(11859,10-11): run-time error JS1010: Expected identifier: =
(11859,10-11): run-time error JS1195: Expected expression: =
(11861,10-11): run-time error JS1010: Expected identifier: =
(11861,10-11): run-time error JS1195: Expected expression: =
(11864,8-9): run-time error JS1010: Expected identifier: .
(11864,8-9): run-time error JS1195: Expected expression: .
(11923,8-9): run-time error JS1010: Expected identifier: .
(11923,8-9): run-time error JS1195: Expected expression: .
(11929,8-9): run-time error JS1010: Expected identifier: .
(11929,8-9): run-time error JS1195: Expected expression: .
(12102,8-9): run-time error JS1010: Expected identifier: .
(12102,8-9): run-time error JS1195: Expected expression: .
(12619,8-9): run-time error JS1010: Expected identifier: .
(12619,8-9): run-time error JS1195: Expected expression: .
(12680,8-9): run-time error JS1010: Expected identifier: .
(12680,8-9): run-time error JS1195: Expected expression: .
(12773,8-9): run-time error JS1010: Expected identifier: .
(12773,8-9): run-time error JS1195: Expected expression: .
(12940,8-9): run-time error JS1010: Expected identifier: .
(12940,8-9): run-time error JS1195: Expected expression: .
(13027,8-9): run-time error JS1010: Expected identifier: .
(13027,8-9): run-time error JS1195: Expected expression: .
(13089,8-9): run-time error JS1010: Expected identifier: .
(13089,8-9): run-time error JS1195: Expected expression: .
(13207,8-9): run-time error JS1010: Expected identifier: .
(13207,8-9): run-time error JS1195: Expected expression: .
(13260,8-9): run-time error JS1010: Expected identifier: .
(13260,8-9): run-time error JS1195: Expected expression: .
(13333,8-9): run-time error JS1010: Expected identifier: .
(13333,8-9): run-time error JS1195: Expected expression: .
(13389,8-9): run-time error JS1010: Expected identifier: .
(13389,8-9): run-time error JS1195: Expected expression: .
(13430,8-9): run-time error JS1010: Expected identifier: .
(13430,8-9): run-time error JS1195: Expected expression: .
(13556,8-9): run-time error JS1010: Expected identifier: .
(13556,8-9): run-time error JS1195: Expected expression: .
(13691,8-9): run-time error JS1010: Expected identifier: .
(13691,8-9): run-time error JS1195: Expected expression: .
(13754,8-9): run-time error JS1010: Expected identifier: .
(13754,8-9): run-time error JS1195: Expected expression: .
(13911,8-9): run-time error JS1010: Expected identifier: .
(13911,8-9): run-time error JS1195: Expected expression: .
(13941,8-9): run-time error JS1010: Expected identifier: .
(13941,8-9): run-time error JS1195: Expected expression: .
(14069,8-9): run-time error JS1010: Expected identifier: .
(14069,8-9): run-time error JS1195: Expected expression: .
(14099,8-9): run-time error JS1010: Expected identifier: .
(14099,8-9): run-time error JS1195: Expected expression: .
(14121,8-9): run-time error JS1010: Expected identifier: .
(14121,8-9): run-time error JS1195: Expected expression: .
(14157,8-9): run-time error JS1010: Expected identifier: .
(14157,8-9): run-time error JS1195: Expected expression: .
(14202,8-9): run-time error JS1010: Expected identifier: .
(14202,8-9): run-time error JS1195: Expected expression: .
(14481,8-9): run-time error JS1010: Expected identifier: .
(14481,8-9): run-time error JS1195: Expected expression: .
(14721,8-9): run-time error JS1010: Expected identifier: .
(14721,8-9): run-time error JS1195: Expected expression: .
(14905,8-9): run-time error JS1010: Expected identifier: .
(14905,8-9): run-time error JS1195: Expected expression: .
(14912,8-9): run-time error JS1010: Expected identifier: .
(14912,8-9): run-time error JS1195: Expected expression: .
(14919,8-9): run-time error JS1010: Expected identifier: .
(14919,8-9): run-time error JS1195: Expected expression: .
(14988,10-11): run-time error JS1010: Expected identifier: =
(14988,10-11): run-time error JS1195: Expected expression: =
(14990,10-11): run-time error JS1010: Expected identifier: =
(14990,10-11): run-time error JS1195: Expected expression: =
(14996,8-9): run-time error JS1010: Expected identifier: .
(14996,8-9): run-time error JS1195: Expected expression: .
(15292,8-9): run-time error JS1010: Expected identifier: .
(15292,8-9): run-time error JS1195: Expected expression: .
(15302,8-9): run-time error JS1010: Expected identifier: .
(15302,8-9): run-time error JS1195: Expected expression: .
(15367,8-9): run-time error JS1010: Expected identifier: .
(15367,8-9): run-time error JS1195: Expected expression: .
(15510,8-9): run-time error JS1010: Expected identifier: .
(15510,8-9): run-time error JS1195: Expected expression: .
(15632,8-9): run-time error JS1010: Expected identifier: .
(15632,8-9): run-time error JS1195: Expected expression: .
(15731,8-9): run-time error JS1010: Expected identifier: .
(15731,8-9): run-time error JS1195: Expected expression: .
(15797,8-9): run-time error JS1010: Expected identifier: .
(15797,8-9): run-time error JS1195: Expected expression: .
(16484,8-9): run-time error JS1010: Expected identifier: .
(16484,8-9): run-time error JS1195: Expected expression: .
(17113,8-9): run-time error JS1010: Expected identifier: .
(17113,8-9): run-time error JS1195: Expected expression: .
(17272,11-12): run-time error JS1010: Expected identifier: .
(17272,11-12): run-time error JS1195: Expected expression: .
(17524,8-9): run-time error JS1010: Expected identifier: .
(17524,8-9): run-time error JS1195: Expected expression: .
(17718,8-9): run-time error JS1010: Expected identifier: .
(17718,8-9): run-time error JS1195: Expected expression: .
(17854,8-9): run-time error JS1010: Expected identifier: .
(17854,8-9): run-time error JS1195: Expected expression: .
(18144,8-9): run-time error JS1010: Expected identifier: .
(18144,8-9): run-time error JS1195: Expected expression: .
(18170,8-9): run-time error JS1010: Expected identifier: .
(18170,8-9): run-time error JS1195: Expected expression: .
(18257,8-9): run-time error JS1010: Expected identifier: .
(18257,8-9): run-time error JS1195: Expected expression: .
(18435,8-9): run-time error JS1010: Expected identifier: .
(18435,8-9): run-time error JS1195: Expected expression: .
(18575,8-9): run-time error JS1010: Expected identifier: .
(18575,8-9): run-time error JS1195: Expected expression: .
(18620,8-9): run-time error JS1010: Expected identifier: .
(18620,8-9): run-time error JS1195: Expected expression: .
(18654,8-9): run-time error JS1010: Expected identifier: .
(18654,8-9): run-time error JS1195: Expected expression: .
(18685,8-9): run-time error JS1010: Expected identifier: .
(18685,8-9): run-time error JS1195: Expected expression: .
(18726,8-9): run-time error JS1010: Expected identifier: .
(18726,8-9): run-time error JS1195: Expected expression: .
(18739,8-9): run-time error JS1010: Expected identifier: .
(18739,8-9): run-time error JS1195: Expected expression: .
(18752,8-9): run-time error JS1010: Expected identifier: .
(18752,8-9): run-time error JS1195: Expected expression: .
(18958,8-9): run-time error JS1010: Expected identifier: .
(18958,8-9): run-time error JS1195: Expected expression: .
(18959,8-9): run-time error JS1010: Expected identifier: .
(18959,8-9): run-time error JS1195: Expected expression: .
(18960,8-9): run-time error JS1010: Expected identifier: .
(18960,8-9): run-time error JS1195: Expected expression: .
(18981,8-9): run-time error JS1010: Expected identifier: .
(18981,8-9): run-time error JS1195: Expected expression: .
(19050,8-9): run-time error JS1010: Expected identifier: .
(19050,8-9): run-time error JS1195: Expected expression: .
(19074,8-9): run-time error JS1010: Expected identifier: .
(19074,8-9): run-time error JS1195: Expected expression: .
(19095,8-9): run-time error JS1010: Expected identifier: .
(19095,8-9): run-time error JS1195: Expected expression: .
(19120,8-9): run-time error JS1010: Expected identifier: .
(19120,8-9): run-time error JS1195: Expected expression: .
(19138,8-9): run-time error JS1010: Expected identifier: .
(19138,8-9): run-time error JS1195: Expected expression: .
(19248,8-9): run-time error JS1010: Expected identifier: .
(19248,8-9): run-time error JS1195: Expected expression: .
(19249,8-9): run-time error JS1010: Expected identifier: .
(19249,8-9): run-time error JS1195: Expected expression: .
(19370,8-9): run-time error JS1010: Expected identifier: .
(19370,8-9): run-time error JS1195: Expected expression: .
(19403,8-9): run-time error JS1010: Expected identifier: .
(19403,8-9): run-time error JS1195: Expected expression: .
(19448,8-9): run-time error JS1010: Expected identifier: .
(19448,8-9): run-time error JS1195: Expected expression: .
(19475,8-9): run-time error JS1010: Expected identifier: .
(19475,8-9): run-time error JS1195: Expected expression: .
(19508,8-9): run-time error JS1010: Expected identifier: .
(19508,8-9): run-time error JS1195: Expected expression: .
(19537,8-9): run-time error JS1010: Expected identifier: .
(19537,8-9): run-time error JS1195: Expected expression: .
(19584,8-9): run-time error JS1010: Expected identifier: .
(19584,8-9): run-time error JS1195: Expected expression: .
(19632,8-9): run-time error JS1010: Expected identifier: .
(19632,8-9): run-time error JS1195: Expected expression: .
(19696,8-9): run-time error JS1010: Expected identifier: .
(19696,8-9): run-time error JS1195: Expected expression: .
(19742,8-9): run-time error JS1010: Expected identifier: .
(19742,8-9): run-time error JS1195: Expected expression: .
(19785,8-9): run-time error JS1010: Expected identifier: .
(19785,8-9): run-time error JS1195: Expected expression: .
(19833,8-9): run-time error JS1010: Expected identifier: .
(19833,8-9): run-time error JS1195: Expected expression: .
(19874,8-9): run-time error JS1010: Expected identifier: .
(19874,8-9): run-time error JS1195: Expected expression: .
(20778,8-9): run-time error JS1010: Expected identifier: .
(20778,8-9): run-time error JS1195: Expected expression: .
(20833,8-9): run-time error JS1010: Expected identifier: .
(20833,8-9): run-time error JS1195: Expected expression: .
(20930,8-9): run-time error JS1010: Expected identifier: .
(20930,8-9): run-time error JS1195: Expected expression: .
(20982,8-9): run-time error JS1010: Expected identifier: .
(20982,8-9): run-time error JS1195: Expected expression: .
(20994,8-9): run-time error JS1010: Expected identifier: .
(20994,8-9): run-time error JS1195: Expected expression: .
(21056,8-9): run-time error JS1010: Expected identifier: .
(21056,8-9): run-time error JS1195: Expected expression: .
(21093,8-9): run-time error JS1010: Expected identifier: .
(21093,8-9): run-time error JS1195: Expected expression: .
(21121,8-9): run-time error JS1010: Expected identifier: .
(21121,8-9): run-time error JS1195: Expected expression: .
(21153,8-9): run-time error JS1010: Expected identifier: .
(21153,8-9): run-time error JS1195: Expected expression: .
(21170,8-9): run-time error JS1010: Expected identifier: .
(21170,8-9): run-time error JS1195: Expected expression: .
(21187,8-9): run-time error JS1010: Expected identifier: .
(21187,8-9): run-time error JS1195: Expected expression: .
(21207,8-9): run-time error JS1010: Expected identifier: .
(21207,8-9): run-time error JS1195: Expected expression: .
(21234,8-9): run-time error JS1010: Expected identifier: .
(21234,8-9): run-time error JS1195: Expected expression: .
(21263,8-9): run-time error JS1010: Expected identifier: .
(21263,8-9): run-time error JS1195: Expected expression: .
(21274,8-9): run-time error JS1010: Expected identifier: .
(21274,8-9): run-time error JS1195: Expected expression: .
(21301,8-9): run-time error JS1010: Expected identifier: .
(21301,8-9): run-time error JS1195: Expected expression: .
(21330,8-9): run-time error JS1010: Expected identifier: .
(21330,8-9): run-time error JS1195: Expected expression: .
(21396,8-9): run-time error JS1010: Expected identifier: .
(21396,8-9): run-time error JS1195: Expected expression: .
(21454,8-9): run-time error JS1010: Expected identifier: .
(21454,8-9): run-time error JS1195: Expected expression: .
(21465,8-9): run-time error JS1010: Expected identifier: .
(21465,8-9): run-time error JS1195: Expected expression: .
(21494,8-9): run-time error JS1010: Expected identifier: .
(21494,8-9): run-time error JS1195: Expected expression: .
 */
(function (angular) {

	'use strict';

	var app = angular.module('hawaiianApp', [
		'ng.shims.placeholder',
		'ngSanitize',
		'haConfigModule',
		'haUtilsModule',
		'haGlobalsModule',
		'haSitecoreModule',
		'haName',
		'haJson',
		'ngPromiseExtras',
		'haTailModule',
		'haDataModule',
		'haDraggableModule',
		'haRevealOnLoadModule',
		'haWhenReadyModule',
		'selectOnClickModule',
		'currencyNoDecimalsFilter',
		'haFootNoteModule',
		'haErrorsModule',
		'haKeyboardModule',
		'haToggleModule',
		'noClickFocusModule',
		'haWindowEventsModule',
		'haCurrencyModule',
		'haLocalizeNameModule',
		'haLocalizeDateModule',
		'haPasswordStrengthModule',
		'haFormValidationModule',
		'haCarouselModule',
		'haCalendarModule',
		'haCalendar2Module',
		'haCalendarEventsModule',
		'shareWidgetModule',
		'haDateInputModule',
		'haLocationInputModule',
		'haRecentSearchesModule',
		'haHelpAndTipsModule',		
		'haAlertModule',
		'haDynamicModalModule',		
		'haCustomDropdownModule',
		'haAvatarModule',
		'haTooltipModule',
		'haGlobalAlertModule',
		'haGlobalMessageModule',
		'haMapNavigatorModule',
		'haErrorPageModule',
		'haProgressBarModule',
		'haStickyBookingWidgetModule',		
		'haPassengerCountModule',
		'haCalendarEventsService',
		'haCustomerSelectionsService',
		'haFavoritesService',
		'haHelpAndTipsService',
		'haModalService',		
		'haSearchCacheService',
		'haSegmentService',
		'haUserService',
		'haGlobalHeaderModule',
		'haHeaderSearchModule',
		'exploreHeroModule',
		'exploreMapModule',
		'haGlobalFooterModule',
		'haFormNativeappLinkSmsModule',
		'haNativeAppModalModule',
		'haBookingHeroModule',
		'haCustomDirectivesModule',
		'haFlightStatusModule',		
		'haReshopSelectionModalModule',
		'haGlobalAlertsModule',
		'haSearchResultsModule',
		'haFeaturedDealsModule',
		'haMainImageContentBlockModule',
		'haCmsChildNavFrontPageModule',
		'haCmsChildNavSubPageModule',
		'haPhotoGalleryModule',		
		'haCmsDetailPageBannerHeadlineModule',
		'haCmsBodyCopyAdModule',
		'haCmsFullWidthPromotionModule',
		'haAdvancedSearchModule',
		'haHomepageModule',
		'haCmsRichTextEditorModule',
		'haCmsBodyCopyWithSidebarModule',
		'haLowFareSearchModule',
		'haFlexiblePriceViewModule',
		'haPriceApiModule',
		'haEcertAPI',
		'ui.mask',
		'ui.keypress',
		'hmTouchEvents',
		'ngTouch',
		'ngAnimate',
		'haPerformanceStatsModule',
		'haDateUtilsModule',
		'haDatepickerModule',
		'haBookingFormModule',
		'haAirportsModule',
		'haInputName',
		'haBookingFormModule',
		'formValidation',
		'haNeatFormsModule',
		'haCartrawlerFormsModule',
		'ui.bootstrap.tabs',
		'ui.bootstrap.accordion',
		'haWatchTrailerModule',
		'haLoadingSpinnerModule',
		'haRegexModule',
		'haHomepageStoriesAndEventsModule',
		'haAriaLiveModule',
		'focusOnDisplayModule',
		'haRoundingFiltersModule',
		'haElementDirectivesModule',
		'haPrimaryNavAccountMenuModule',
		'haPrimaryNavAlertsModule',
		'haKisaTermsModule',
		'haBodyExtensionModule',
		'haSessionTimeoutAPI',
		'haMobileService',
		'haAirportOrAddressInputModule',
		'haHotelsInputModule',
		'haButtonSpinnerModule',
		'haRVFormModule',
        'haIslandGuideMapModule',
		'haDisableOnClickModule',
		'TitleCaseModule',
		'haEncryptionModule',
		'haLaunchDarklyModule',
		'haTimedHideModule',
		'haDealTilesModule',
		'haResponsiveAttributeModule',
		'haPairLinksModule',
		'haDomModalModule',
		'bookingWidgetSlimModule',
		'haDelayAutoplayModule',
		'haAccordionModule'
	]);

	app.config([
		'$logProvider',
		'$sceProvider',
		'$anchorScrollProvider',
		'$provide',
		function ($logProvider, $sceProvider, $anchorScrollProvider, $provide) {
			// Enable debug logging for 'local.*' hostnames, or with #debug URL hash...disable for prod.
			var isDebug = (window.location.search.indexOf('debug') >= 0);
			var isLocal = (window.location.hostname.indexOf('local') === 0);
			$logProvider.debugEnabled(isDebug || isLocal);

			// Completely disable SCE to support IE7.
			$sceProvider.enabled(false);

			$anchorScrollProvider.disableAutoScrolling();

			$provide.decorator('ngModelDirective', ['$delegate', function ($delegate) {
				var ngModel = $delegate[0];
				var controller = ngModel.controller;

				ngModel.controller = ['$scope', '$element', '$attrs', '$injector',
					function (scope, element, attrs, $injector) {
						var name = attrs.name;
						if (name != null && (name.indexOf('{{') > -1)) {
							var $interpolate = $injector.get('$interpolate');
							attrs.$set('name', $interpolate(name)(scope));
						}
						$injector.invoke(controller, this, {
							'$scope': scope,
							'$element': element,
							'$attrs': attrs
						});
					}
				];
				return $delegate;
			}]);

			$provide.decorator('formDirective', ['$delegate', function ($delegate) {
				var form = $delegate[0];
				var controller = form.controller;

				form.controller = ['$scope', '$element', '$attrs', '$injector',
					function (scope, element, attrs, $injector) {
						var nameOrForm = attrs.name || attrs.ngForm;
						if (nameOrForm != null && nameOrForm.indexOf('{{') > -1) {
							var $interpolate = $injector.get('$interpolate');
							attrs.$set('name', $interpolate(nameOrForm)(scope));
						}
						$injector.invoke(controller, this, {
							'$scope': scope,
							'$element': element,
							'$attrs': attrs
						});
					}
				];
				return $delegate;
			}]);
		}
	]);

	app.run([
		'$q',
		'$rootScope',
		'haModal',
		'haGlobals',
		'haCitiesSvc',
		'haUtils',
        'haMobileSvc',
		'haConfig',

		function ($q, $rootScope, haModal, haGlobals, haCitiesSvc, haUtils, haMobileSvc, haConfig) {
			if (typeof Promise === 'undefined') {
				//make like ES6 promises (actually copied from Angular 1.4
				window.Promise = function (fn) {
					var d = $q.defer();
					fn(function (v) {
						d.resolve(v);
					}, function (v) {
						d.reject(v);
					});
					return d.promise;
				};
				Promise.resolve = function (val) {
					return new Promise(function (resolve) {
						resolve(val);
					});
				};
				Promise.reject = $q.reject;
				Promise.all = $q.all;
			}

			$rootScope.haModal = haModal;
			$rootScope.haCitiesSvc = haCitiesSvc;

			haGlobals(['$language', '$currency'], function (l, c) {
				$rootScope.$language = l;
				$rootScope.$currency = c;
			});

			$rootScope.getMediaImage = function (str) {
				return haUtils.getImageFromSiteCoreString(str);
			};

			$rootScope.getTemplateUrl = haConfig.getTemplateUrl;
			$rootScope.getRazorTemplateUrl = haConfig.getRazorTemplateUrl;
			$rootScope.getImgUrl = haConfig.getImgUrl;
			$rootScope.csrf = window.tokens;
			$rootScope.moment = moment;

            $rootScope.isMobile = haMobileSvc.determineIsMobile();

            angular.element(window).bind('resize', function () {
                $rootScope.isMobile = haMobileSvc.determineIsMobile();
                $rootScope.$apply();
            });

		}
	]);

})(angular);
;
(function (angular) {

	// Ha Cms Rich Text Editor
	// --------------------------------------------
	//
	// * **Class:** HaCmsRichTextEditor
	// * **Author:** Melissa Rota
	//
	// CMS Component for entering HTML rich text

	'use strict';

	var mod = angular.module('haCmsRichTextEditorModule', []);

	mod.directive('haCmsRichTextEditor', ['$compile', function ($compile) {

		var HaCmsRichTextEditorController = function ($scope) {
			$scope.$emit('$haCmsRichTextEditorReady');
		};

		HaCmsRichTextEditorController.$inject = ['$scope'];

		var HaCmsRichTextEditorLink = function ($scope, $element) {

			// NEP: Hack to inject ha-scroll-to into ha-cms-rich-text-editor for internal hrefs
			$element.find('a').each(function (idx, el) {
				var href = $(el).attr('href');
				if ((typeof href === 'string') && (href.indexOf('#') === 0)) {
					$(el).attr('ha-scroll-to', href.slice(1));
					$compile(el)($scope);
				}
			});

			// set border class for two column layout
			var $el = $($element);
			var parentColumn = $el.closest('.column');
			if (parentColumn.length > 0) {
				var twoColumnSublayout = $el.closest('.two-column-sublayout');
				// check if in two col and richTextCol has not been added already
				if (twoColumnSublayout.length > 0 && twoColumnSublayout.find('.rich-text-col').length === 0) {
					twoColumnSublayout.find('.column').removeClass('default-split');
					parentColumn.addClass('rich-text-col');
				}
			}
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaCmsRichTextEditorLink,
			controller: HaCmsRichTextEditorController
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Advanced Search
	// --------------------------------------------
	//
	// * **Class:** HaAdvancedSearch
	// * **Author:** Michael Toymil
	//
	// Controls the advanced search page.

	'use strict';

	var module = angular.module('haAdvancedSearchModule', []);

	module.directive('haAdvancedSearch', ['haSitecoreStrings', 'haGlobals', '$interpolate', function ($scs, haGlobals, $interpolate) {

		var HaAdvancedSearchController = function ($scope) {

			haGlobals('ETCOResponseModel', function (etcoResponseModel) {
				$scope.ETCOResponseModel = etcoResponseModel;

				$scs.get('ReservationsTravelCreditRedemption.GetOffAllRoutesText').then(function (txt) {
					$scope.ecertTitle = $interpolate(txt)($scope);
				});
			});

			// console.log('ssssstrings!');
			// console.log($scope.strings);

			$scope.enableHotelsWidget = $scope.$switch('BookingWidget:enablehotelwidget');
			$scope.enableCarsWidget = $scope.$switch('BookingWidget:enablecarwidget');
			$scope.enablePackagesWidget = $scope.$switch('BookingWidget:enablepackageswidget');

			$scope.$on('$bookingWidgetReady', function () {
				$('.advWidgetFooter').show();
				$('.initLoader').hide();
			});

			$scope.$emit('$haAdvancedSearchReady');


			$scope.doSubmit = function (/* $event */) {
				// NEP: Awful, I know. It would help if the submit button lived inside the form!
				setTimeout(function () {
					$('form[name="flightSearch"]').trigger('submit');
				}, 0);
			};
		};


		HaAdvancedSearchController.$inject = ['$scope'];

		var HaAdvancedSearchLink = function ($scope /* , $el */) {

			$scope.exampleMethod = function () {
				return $scope;
			};

		};

		return {
			restrict: 'A',
			scope: true,
			link: HaAdvancedSearchLink,
			controller: HaAdvancedSearchController
		};
	}]);
})(angular);
;
(function (angular) {

	// Ha Cms Full Width Promotion
	// --------------------------------------------
	//
	// * **Class:** HaCmsFullWidthPromotion
	// * **Author:** Chad Kumabe
	//
	// Full-Width Promotion for CMS

	'use strict';

	var mod = angular.module('haCmsFullWidthPromotionModule', []);

	mod.directive('haCmsFullWidthPromotion', function () {

		var HaCmsFullWidthPromotionController = function ($scope) {
			$scope.$emit('$haCmsFullWidthPromotionReady');
		};

		HaCmsFullWidthPromotionController.$inject = ['$scope'];

		var HaCmsFullWidthPromotionLink = function ($scope) {

			$scope.exampleMethod = function () {
				return $scope;
			};
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaCmsFullWidthPromotionLink,
			controller: HaCmsFullWidthPromotionController
		};
	});

})(angular);
;
(function (angular) {

	// Ha Cms Body Copy Ad
	// --------------------------------------------
	//
	// * **Class:** HaCmsBodyCopyAd
	// * **Author:** Chad Kumabe
	//
	// Body Copy Ad for CMS

	'use strict';

	var mod = angular.module('haCmsBodyCopyAdModule', []);

	mod.directive('haCmsBodyCopyAd', function () {

		var HaCmsBodyCopyAdController = function ($scope) {
			$scope.$emit('$haCmsBodyCopyAdReady');
		};

		HaCmsBodyCopyAdController.$inject = ['$scope'];

		var HaCmsBodyCopyAdLink = function ($scope) {

			$scope.exampleMethod = function () {
				return $scope;
			};
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaCmsBodyCopyAdLink,
			controller: HaCmsBodyCopyAdController
		};
	});

})(angular);
;
(function (angular) {

	// Ha Cms Detail Page Banner Headline
	// --------------------------------------------
	//
	// * **Class:** HaCmsDetailPageBannerHeadline
	// * **Author:** Chad Kumabe
	//
	// Detail Page Banner Headline for CMS

	'use strict';

	var mod = angular.module('haCmsDetailPageBannerHeadlineModule', []);

	mod.directive('haCmsDetailPageBannerHeadline', function () {

		var HaCmsDetailPageBannerHeadlineController = function ($scope) {
			$scope.$emit('$haCmsDetailPageBannerHeadlineReady');

		};

		HaCmsDetailPageBannerHeadlineController.$inject = ['$scope'];

		var HaCmsDetailPageBannerHeadlineLink = function ($scope) {

			$scope.exampleMethod = function () {
				return $scope;
			};

			$scope.$on('hacontactusEmailSuccess', function (currentScope, args) {

				if (args.showMessageFlag === 'SUCCESS' || args.showMessageFlag === 'FAIL') {
					$scope.showmessageContentFlag = args.showMessageFlag;
					$scope.showmessageContent = args.showMessage;

				}

			});
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaCmsDetailPageBannerHeadlineLink,
			controller: HaCmsDetailPageBannerHeadlineController
		};
	});

})(angular);
;
(function (angular) {

	// Ha Photo Gallery
	// --------------------------------------------
	//
	// * **Class:** HaPhotoGallery
	// * **Author:** Melissa Rota
	//
	// Modal with photo slideshow.

	'use strict';

	var module = angular.module('haPhotoGalleryModule', [
		'haCarouselModule'
	]);

	module.directive('haPhotoGallery', ['haGlobals', function (haGlobals) {

		var HaPhotoGalleryController = function ($scope) {
			$scope.$emit('$haPhotoGalleryReady');
		};

		HaPhotoGalleryController.$inject = ['$scope'];

		var HaPhotoGalleryLink = function ($scope, $el) {
			$scope.showPhotoGallery = function () {
				$scope.isVisible = true;
				setTimeout(setModalSize(), 200);
			};


			$scope.exampleMethod = function () {
				return $scope;
			};

			$scope.PhotoGallery = [];
			haGlobals('PhotoGalleryVM', function (PhotoGalleryVM) {
				$scope.PhotoGallery = PhotoGalleryVM;
			});

			$scope.slides = $el.find('.ha-carousel-slide');

			$scope.closeBtnClick = function () {
				$scope.isVisible = false;
			};
			// handle resize and modal cover
			var setModalSize = function () {
				if ($('.ha-photo-gallery .is-mobile').css('display') === 'none') {
					var backdropHeight = $(window).height();
					$('.ha-photo-gallery img').css('max-height', backdropHeight - 100);
					$('.ha-photo-gallery .ha-carousel-slide').css('height', backdropHeight);
					$('.ha-photo-gallery .ha-carousel-slide .main-content .paddle').css('height', backdropHeight);
				}
			};
			$(window).resize(function () {
				setModalSize();
			});
			$scope.showPhotoGallery();
			setModalSize();
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaPhotoGalleryLink,
			controller: HaPhotoGalleryController
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Cms Child Nav Sub Page
	// --------------------------------------------
	//
	// * **Class:** HaCmsChildNavSubPage
	// * **Author:** Chad Kumabe
	//
	// Child Navigation Component on a Standard Sub-page

	'use strict';

	var module = angular.module('haCmsChildNavSubPageModule', []);

	module.directive('haCmsChildNavSubPage', function () {

		var HaCmsChildNavSubPageController = function ($scope) {
			$scope.$emit('$haCmsChildNavSubPageReady');

			// if all html rendered by server you can just start here
			// equal to tallest
			var tiles = $('.child-nav-tile');
			var heights = [];
			for (var j = 0; j < tiles.length; j++) {
				var $tile = $(tiles[j]);
				heights.push($tile.height());
			}
			tiles.css('height', Math.max.apply(null, heights));
			// end here
		};

		HaCmsChildNavSubPageController.$inject = ['$scope'];

		var HaCmsChildNavSubPageLink = function ($scope) {

			$scope.exampleMethod = function () {
				return $scope;
			};
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaCmsChildNavSubPageLink,
			controller: HaCmsChildNavSubPageController
		};
	});

})(angular);
;
(function (angular) {

	// Ha Cms Child Nav Front Page
	// --------------------------------------------
	//
	// * **Class:** HaCmsChildNavFrontPage
	// * **Author:** Chad Kumabe
	//
	// Child Navigation Component on a Section Front Page

	'use strict';

	var module = angular.module('haCmsChildNavFrontPageModule', []);

	module.directive('haCmsChildNavFrontPage', function () {

		var HaCmsChildNavFrontPageController = function ($scope) {
			$scope.$emit('$haCmsChildNavFrontPageReady');

			// if all html rendered by server you can just start here
			// equal to tallest
			var tiles = $('.child-nav-tile');
			var heights = [];
			for (var j = 0; j < tiles.length; j++) {
				var $tile = $(tiles[j]);
				heights.push($tile.height());
			}
			$('.child-nav-tile').css('height', Math.max.apply(null, heights));
			// end here
		};

		HaCmsChildNavFrontPageController.$inject = ['$scope'];

		var HaCmsChildNavFrontPageLink = function ($scope) {

			$scope.exampleMethod = function () {
				return $scope;
			};


		};

		return {
			restrict: 'A',
			scope: true,
			link: HaCmsChildNavFrontPageLink,
			controller: HaCmsChildNavFrontPageController
		};
	});

})(angular);
;
(function (angular) {

	// Ha Main Image Content Block
	// --------------------------------------------
	//
	// * **Class:** HaMainImageContentBlock
	// * **Author:** Chad Kumabe
	//
	// Main Image Content Block for CMS

	'use strict';

	var module = angular.module('haMainImageContentBlockModule', []);

	module.directive('haMainImageContentBlock', function () {

		var HaMainImageContentBlockController = function ($scope) {
			$scope.$emit('$haMainImageContentBlockReady');
		};

		HaMainImageContentBlockController.$inject = ['$scope'];

		var HaMainImageContentBlockLink = function ($scope, $el) {

			$scope.slides = $el.find('.ha-carousel-slide');

			$scope.exampleMethod = function () {
				return $scope;
			};
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaMainImageContentBlockLink,
			controller: HaMainImageContentBlockController
		};
	});

})(angular);
;
(function (angular) {

	'use strict';

	var module = angular.module('haFeaturedDealsModule', ['haCarouselModule']);

	module.directive('haFeaturedDeals', [ function () {


		var HaFeaturedDealsLink = function ($scope, $el, $attrs) {

			var viewModel = angular.fromJson($attrs.dealsData),
				tileCount = viewModel.DealTiles ? viewModel.DealTiles.length : 99;

			// Labels
			$scope.toLabelText = $attrs.toLabelText;
			$scope.fromLabelText = $attrs.fromLabelText;
			$scope.perPersonLabelText = $attrs.perPersonLabelText;
			$scope.referenceMark = $attrs.referenceMark;
			$scope.defaultColumns = 12 / viewModel.PerPage;

			// Short circuit display in certain cases - set 50% width
			if (tileCount < 3) {
				$scope.defaultColumns = 6;
			}

			$scope.dealType = viewModel.TileSize || 'Small'; // Small or Large - case sensitive
			$scope.deals = viewModel.DealTiles;

			var dealGroups = [];
			var groupIndex = -1;

			for (var i = 0; i < $scope.deals.length; i++) {
				var deal = $scope.deals[i];
				if (i % viewModel.PerPage === 0) {
					dealGroups.push({ group: [] });
					groupIndex++;
				}
				dealGroups[groupIndex].group.push(deal);
			}
			$scope.dealGroups = dealGroups;
			$scope.slides = dealGroups;
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaFeaturedDealsLink
		};
	}]);

	module.directive('haPromoTile', ['$rootScope', '$filter', '$timeout', 'haConfig', function ($rootScope, $filter, $timeout, haConfig) {

		var HaPromoTileLink = function ($scope, $el, $attr) {

			var model = $scope.$eval($attr.model);

			$scope.model = model;

			// Labels/Images
			model.bgImage = { 'background-image': 'url(\'' + model.BackgroundImageURL + '\')' };
			model.iconUrl = { 'background-image': 'url(\'' + model.IconURL + '\')' };
			model.promoType = (model.TripType === 1) ? $scope.oneWayLabelText : $scope.roundTripLabelText;

			// Allow for other types
			$scope.dealType = (model.TripType === 3) ? 'RichText' : $scope.dealType;
			//$scope.dealType = 'RichText';

			model.LowestFare = Math.min(model.MainCabinBasicFare || Number.POSITIVE_INFINITY, model.Fare);

			// Ref Marks/Disclaimers
			$scope.referenceMark = $scope.referenceMark || '*';
			if (!$rootScope.footnotes || !$rootScope.footnotes.numeric || !$rootScope.references) {
				$rootScope.footnotes = { numeric: [] };
				$rootScope.references = {};
			}

			if (!$rootScope.references[model.DisclaimerGuid]) {

				if (model.Disclaimer) {
					$rootScope.references[model.DisclaimerGuid] = $scope.referenceMark + (Object.keys($rootScope.references).length + 1);
					model.DisclaimerReferenceMark = $rootScope.references[model.DisclaimerGuid];

					var isMCBFare = model.LowestFare && model.LowestFare === model.MainCabinBasicFare;
					var lowFareDisclaimer = isMCBFare ? model.MainCabinBasicDisclaimer : model.Disclaimer;
					$rootScope.footnotes.numeric.push({
						id: '*' + ($rootScope.footnotes.numeric.length + 1),
						text: lowFareDisclaimer
					});
				}

			} else {
				model.DisclaimerReferenceMark = $rootScope.references[model.DisclaimerGuid];
			}

			// Navigation
			$scope.navigateTo = function (deal) {

				// Timeout to let the drag events first first
				$timeout(function () {

					if ($scope.isAnimating) {
						return;
					}

					var target;

					if (deal.CallToActionType > 0) {

						// searchDetails=o=LAX;d=HNL;dd=12/26/2014;a=1;c=0;rd=12/29/2014;tt=2
						var departureDate = ';dd=' + $filter('date')(deal.DepartureDate, 'MM/dd/yyyy');
						var returnDate = (+model.TripType === 1) ? ';rd=' + $filter('date')(deal.ReturnDate, 'MM/dd/yyyy') : '';
						var searchString = ['searchDetails=o=', deal.OriginAirportCode, ';d=', deal.DestinationAirportCode, departureDate, returnDate, ';a=1;c=0;tt=', (+deal.TripType === 0) ? '1' : '2'].join('');

						target = '/book/home?' + searchString;
					} else {
						target = deal.CTALinkURL;
					}

					location.href = target;
				}, 10);
			};
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaPromoTileLink,
			templateUrl: haConfig.getTemplateUrl('ha-promo-tile-base-template.html')
		};
	}]);

})(angular);

;
(function (angular) {

	// Ha Search Results
	// --------------------------------------------
	//
	// * **Class:** HaSearchResults
	// * **Author:** Melissa Rota
	//
	// Displays site search results.

	'use strict';

	var module = angular.module('haSearchResultsModule', []);

	module.directive('haSearchResults', ['haGlobals', 'haGlobalHeaderAPI', function (haGlobals, haGlobalHeaderAPI) {

		var HaSearchResultsController = function ($scope) {

			/* globals s_gi, s_account */

			$scope.googleResult = null;
			haGlobals('GoogleSearchResult', function (GoogleSearchResult) {
				$scope.googleResult = GoogleSearchResult;
			});

			$scope.$emit('$haSearchResultsReady');
			$scope.pagecount = 0;
			$scope.totalrecords = 0;
			$scope.pages = [];
			$scope.selectedpage = 1;
			$scope.startPageIndex = 1;
			var pagecount = 0;
			$scope.IsServiceErrors = false;
			$scope.ServiceErrorMessage = '';


			$scope.UpdateAll = function () {
				if ($scope.googleResult != null) {
					if ($scope.googleResult.ErrorMessage) {
						$scope.IsServiceErrors = true;
						$scope.ServiceErrorMessage = $scope.googleResult.ErrorMessage;
					}
					else if ($scope.googleResult.ErrorMessage == null && $scope.googleResult.items != null) {
						$scope.pagesize = +$scope.googleResult.queries.request[0].count;
						$scope.initialSearchText = $scope.googleResult.queries.request[0].searchTerms;
						$scope.lastSearchText = $scope.initialSearchText;
						$scope.totalrecords = $scope.googleResult.queries.request[0].totalResults;
						pagecount = Math.ceil($scope.totalrecords / $scope.pagesize);
						$scope.totalPageCount = pagecount;
						document.body.dispatchEvent(new CustomEvent('SearchResultFired', { 'detail': $scope.totalrecords }));
						if (pagecount < $scope.pagesize) {
							$scope.pagesize = pagecount;
						}
						$scope.pagecount = pagecount;
						if ($scope.pagesize <= $scope.pagecount) {
							$scope.pages.splice(0, 15);
							for (var counter = $scope.startPageIndex; counter <= $scope.pagesize; counter++) {
								$scope.pages.push(counter);
							}
							//reset pagination value.
							$scope.selectedpage = $scope.pages[0];
							if ($scope.selectedpage === 1) {
								$scope.setPrvDisabled = true;
							}
							if ($scope.pages.length <= 1) {
								$scope.setNxtDisabled = true;
							}
						}
						if ($scope.googleResult.items != null) {
							$scope.SearchIndex = $scope.googleResult.queries.request[0];
							// CS: QUERY: JSHint: Confusing plusses
							// $scope.SearchTerms = +$scope.googleResult.queries.request[0].startIndex + +$scope.SearchIndex.count - 1;
							$scope.SearchTerms = parseInt($scope.googleResult.queries.request[0].startIndex, 10) + parseInt($scope.SearchIndex.count, 10) - 1;
							$scope.itemsPerPage = $scope.googleResult.items.length;
						}
					}
				}
			};

			$scope.NextOrPreviousOrNewSearchText = function (newSearchQuery) {
				haGlobalHeaderAPI.searchGoogle(newSearchQuery).success(function (result) {
					if (result.ErrorMessage) {
						$scope.IsServiceErrors = true;
						$scope.ServiceErrorMessage = result.ErrorMessage;
					}
					else if (result) {
						$scope.googleResult = result;
						$scope.SearchIndex = $scope.googleResult.queries.request[0];
						// CS: QUERY: JSHint: Confusing plusses
						// $scope.SearchTerms = +$scope.googleResult.queries.request[0].startIndex + +$scope.SearchIndex.count - 1;
						$scope.SearchTerms = parseInt($scope.googleResult.queries.request[0].startIndex) + parseInt($scope.SearchIndex.count) - 1;
					}
					if (!$scope.$root.isMobile) {
						$('body, html').animate({
							scrollTop: 350
						}, 'slow');
					}
				});
			};

			$scope.NavigateToPage = function (page) {
				if (page > 1) {
					$scope.selectedpage = page;
					var newSearch = $scope.SearchIndex.searchTerms;
					var pageNumber = ($scope.selectedpage - 1) * $scope.itemsPerPage + 1;
					newSearch = newSearch + '&start=' + pageNumber + '&sa=N';
					$scope.googleResult.SearchPhrase = $scope.SearchIndex.searchTerms;
					$scope.NextOrPreviousOrNewSearchText(newSearch);
					$scope.disableNextPagination();
					$scope.disablePreviousPagination();
				} else {
					$scope.selectedpage = page;
					var newSear = $scope.SearchIndex.searchTerms;
					$scope.NextOrPreviousOrNewSearchText(newSear);
				}
			};

			//-Build google search quary
			$scope.nextResult = function () {
				var newSearch = $scope.SearchIndex.searchTerms;
				var pageNumber = $scope.selectedpage * $scope.itemsPerPage + 1;
				newSearch = newSearch + '&start=' + pageNumber + '&sa=N';
				$scope.googleResult.SearchPhrase = $scope.SearchIndex.searchTerms;
				$scope.NextOrPreviousOrNewSearchText(newSearch);
				$scope.disableNextPagination();
			};

			$scope.newPreviousResult = function () {
				var newSearch = $scope.SearchIndex.searchTerms;
				var pageNumber = ($scope.selectedpage - 2) * $scope.itemsPerPage + 1;
				newSearch = newSearch + '&start=' + pageNumber + '&sa=N';
				$scope.googleResult.SearchPhrase = $scope.SearchIndex.searchTerms;
				$scope.NextOrPreviousOrNewSearchText(newSearch);
				$scope.selectedpage--;
				$scope.disablePreviousPagination();
				$scope.disableNextPagination();
			};

			$scope.NavigateByStep = function (oper) {
				var curpage = $scope.selectedpage;
				if (oper === 'next') {
					if (!$scope.setNxtDisabled && $scope.selectedpage < 10) {
						if (curpage >= $scope.pagesize && curpage <= $scope.totalPageCount) {
							$scope.pa = $scope.pages.splice(1, 1);
							$scope.pages.splice(0, 15);

							for (var newCounter = $scope.pa[0]; newCounter <= $scope.pagesize + 1; newCounter++) {
								$scope.pages.push(newCounter);
							}
							$scope.nextResult();
							$scope.pagesize++;
							curpage++;
							$scope.selectedpage = curpage;
							if ($scope.selectedpage > 1) {
								$scope.setPrvDisabled = false;
							}
						} else {
							$scope.nextResult();
							curpage++;
							$scope.selectedpage = curpage;
							if ($scope.selectedpage > 1) {
								$scope.setPrvDisabled = false;
							}
						}
					}
				} else if (oper === 'prev') {
					if (!$scope.setPrvDisabled) {
						if (curpage >= $scope.pagesize) {
							var prve = $scope.pages[0];
							prve--;
							if (prve > 0) {
								$scope.pa = $scope.pages.splice(9, 1);
								$scope.pages.splice(0, 15);
								for (var newCount = prve; newCount <= $scope.pagesize - 1; newCount++) {
									$scope.pages.push(newCount);
								}
								$scope.newPreviousResult();
								$scope.pagesize--;
								curpage--;

								$scope.selectedpage = curpage;
							} else {
								$scope.newPreviousResult();
								curpage--;
								$scope.selectedpage = curpage;
							}
						} else {
							$scope.newPreviousResult();
							curpage--;
							$scope.selectedpage = curpage;
						}
					}
				}
			};
			$scope.searchSubmitBtnClick = function () {
				if ($scope.googleResult.SearchPhrase === $scope.lastSearchText) {
					return false;
				}

				var newSearchText = $scope.googleResult.SearchPhrase;

				if (newSearchText !== '' && newSearchText !== $scope.lastSearchText) {
					$scope.lastSearchText = newSearchText;
					haGlobalHeaderAPI.searchGoogle(newSearchText).success(function (result) {
						if (result.ErrorMessage || result.TranslateServiceError !== '') {
							$scope.IsServiceErrors = true;
							$scope.ServiceErrorMessage = result.ErrorMessage;
							document.body.dispatchEvent(new CustomEvent('SearchResultFired', { 'detail': '0' }));
						}
						else if (result) {
							$scope.googleResult = result;
							$scope.IsServiceErrors = false;
							$scope.setNxtDisabled = false;
							$scope.pagesize = +$scope.googleResult.queries.request[0].count;
							$scope.pagecount = $scope.pagesize;
							$scope.UpdateAll();
							var totalRecords = $scope.googleResult.queries.request[0].totalResults;
							document.body.dispatchEvent(new CustomEvent('SearchResultFired', { 'detail': totalRecords }));
						}
						if (!$scope.$root.isMobile) {
							$('body, html').animate({
								scrollTop: 350
							}, 'slow');
						}
					});
				}
			};
			$scope.UpdateAll();

		};

		HaSearchResultsController.$inject = ['$scope'];

		var HaSearchResultsLink = function ($scope, $el) {

			$scope.exampleMethod = function () {
				return $scope;
			};

			$scope.$searchInput = $el.find('.search-field');
			$scope.$searchSubmitBtn = $el.find('.search-submit-btn');
			$scope.lastindex = $el.find('.last-index');
			$scope.firstindex = $el.find('.first-index');


			if ($scope.pages.length <= 1) {
				$scope.setNxtDisabled = true;
				$scope.lastindex.addClass('disabled');
			} else {
				$scope.setNxtDisabled = false;
			}

			$scope.setPrvDisabled = true;
			$scope.firstindex.addClass('disabled');

			$scope.onSearchFieldKeyup = function () {
				if ($scope.googleResult.SearchPhrase === '') {
					$scope.$searchSubmitBtn.addClass('disabled');
				} else {
					$scope.$searchSubmitBtn.removeClass('disabled');
				}
			};
			$scope.disableNextPagination = function () {
				if ($scope.pagecount > $scope.selectedpage) {
					$scope.lastindex.removeClass('disabled');
					$scope.setNxtDisabled = false;
				}
				else if ($scope.pagecount <= $scope.selectedpage + 1) {
					$scope.lastindex.addClass('disabled');
					$scope.setNxtDisabled = true;
				}
				else {
					$scope.setNxtDisabled = false;
				}
			};

			$scope.disablePreviousPagination = function () {
				if ($scope.selectedpage >= 2) {
					if ($scope.selectedpage <= $scope.startPageIndex) {
						$scope.firstindex.addClass('disabled');
						$scope.setPrvDisabled = true;
					}
					else if ($scope.selectedpage >= 2) {
						$scope.firstindex.removeClass('disabled');
						$scope.setPrvDisabled = false;
					}
				}
				else if ($scope.selectedpage <= 1) {
					$scope.firstindex.addClass('disabled');
					$scope.setPrvDisabled = true;
				}
			};
			$scope.activePage = function () {


			};

		};
		return {
			restrict: 'A',
			scope: true,
			link: HaSearchResultsLink,
			controller: HaSearchResultsController
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Global Alerts
	// --------------------------------------------
	//
	// * **Class:** HaGlobalAlerts
	// * **Author:** Melissa Rota & Nathan Probst
	//
	// Alerts that can be displayed above the header across pages

	'use strict';

	var module = angular.module('haGlobalAlertsModule', []);

	module.directive('haGlobalAlerts', [
		function () {
			var HaGlobalAlertsController = [
				'$scope',
				'$rootScope',

				function ($scope, $rootScope) {
					$rootScope.haGlobalAlerts = {};

					this.updateHeight = function (clientHeight) {
						$rootScope.haGlobalAlerts.clientHeight = clientHeight;
					};
				}
			];

			return {
				restrict: 'A',
				require: 'haGlobalAlerts',
				controller: HaGlobalAlertsController,
				link: function ($scope, $el, $attrs, ctrl) {
					var el = $el[0];
					ctrl.updateHeight($el.innerHeight());

					var first = true;
					$scope.$watch(function () {
							return $el.innerHeight();
						},
						function (newHeight, oldHeight) {
							if (first || (newHeight !== oldHeight)) {
								first = false;
								ctrl.updateHeight(newHeight);
							}
						});
				}
			};
		}
	]);

})(angular);
;
(function (angular) {

	// Ha Reshop Selection Modal
	// --------------------------------------------
	//
	// * **Class:** HaReshopSelectionModal
	// * **Author:** Melissa Rota
	//
	// Modal to select passengers and flights to change.

	'use strict';

	var module = angular.module('haReshopSelectionModalModule', []);

	module.directive('haReshopSelectionModal', function () {

		var HaReshopSelectionModalController = function ($scope) {
			$scope.$emit('$haReshopSelectionModalReady');
		};

		HaReshopSelectionModalController.$inject = ['$scope'];

		var HaReshopSelectionModalLink = function ($scope, $el) {

			$scope.exampleMethod = function () {
				return $scope;
			};

			$scope.$paxSelect = $el.find('.pax-select');
			$scope.$flightSelect = $el.find('.flight-select');
			$scope.$seeFlightsBtn = $el.find('.see-flights-btn');
			$scope.$continueBtn = $el.find('.continue-btn');

			$scope.selectedPax = [0, 1];
			$scope.selectedLegs = [];

			$scope.closeChangeFlightsBtnClick = function () {
				$el.hide();
			};

			$scope.continueBtnClick = function () {
				$scope.transitionFromPaxToFlights();
			};

			$scope.transitionFromPaxToFlights = function () {
				$scope.$paxSelect.addClass('hide');
				$scope.$flightSelect.addClass('expand');
			};

			$scope.enableContinueBtn = function (enable) {
				if (enable) {
					$scope.$continueBtn.removeClass('disabled');
				}
				else {
					$scope.$continueBtn.addClass('disabled');
				}
			};

			$scope.togglePaxSelect = function (paxIndex) {
				if ($scope.isPaxSelected(paxIndex)) {
					$scope.removePax(paxIndex);
				}
				else {
					$scope.addPax(paxIndex);
				}
				$scope.enableContinueBtn($scope.selectedPax.length > 0);
			};

			$scope.addPax = function (paxIndex) {
				if ($scope.selectedPax.indexOf(paxIndex) === -1) {
					$scope.selectedPax.push(paxIndex);
				}
			};

			$scope.removePax = function (paxIndex) {
				if ($scope.selectedPax.indexOf(paxIndex) !== -1) {
					$scope.selectedPax.splice($scope.selectedPax.indexOf(paxIndex), 1);
				}
			};

			$scope.isPaxSelected = function (paxIndex) {
				return $scope.selectedPax.indexOf(paxIndex) >= 0;
			};

			$scope.showFlightControlsForLeg = function (legIndex) {
				$scope.$currentLegControls = $el.find('.leg-' + legIndex + '-controls');

				if ($scope.isLegSelected(legIndex)) {
					$scope.removeLeg(legIndex);
					$scope.$currentLegControls.removeClass('expand');
				}
				else {
					$scope.addLeg(legIndex);
					$scope.$currentLegControls.addClass('expand');
				}

				$scope.enableSeeFlightsBtn($scope.selectedLegs.length > 0);

			};

			$scope.enableSeeFlightsBtn = function (enable) {
				if (enable) {
					$scope.$seeFlightsBtn.removeClass('disabled');
				}
				else {
					$scope.$seeFlightsBtn.addClass('disabled');
				}
			};

			$scope.addLeg = function (legIndex) {
				if ($scope.selectedLegs.indexOf(legIndex) === -1) {
					$scope.selectedLegs.push(legIndex);
				}
			};

			$scope.removeLeg = function (legIndex) {
				if ($scope.selectedLegs.indexOf(legIndex) !== -1) {
					$scope.selectedLegs.splice($scope.selectedLegs.indexOf(legIndex), 1);
				}
			};

			$scope.isLegSelected = function (legIndex) {
				return $scope.selectedLegs.indexOf(legIndex) >= 0;
			};
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaReshopSelectionModalLink,
			controller: HaReshopSelectionModalController
		};
	});

})(angular);
;
(function (angular) {

	// Ha Flight Status
	// --------------------------------------------
	//
	// * **Class:** HaFlightStatus
	// * **Author:** Melissa Rota
	//
	// Check flight status by route or flight number.

	'use strict';

	var module = angular.module('haFlightStatusModule', []);

	module.directive('haFlightStatus', ['haGlobals', '$window', function (haGlobals, $window) {

		// globals
		var serverShortDate = $window.serverShortDate;

		var HaFlightStatusController = function ($scope, haCustomerSelections) {
			haGlobals('jsonFSModel', function (jsonFSModel) {
				$scope.FSByFlightNoModel = jsonFSModel;
			});
			if (typeof (Storage) !== 'undefined') {
				// Code for localStorage/sessionStorage.
				try {
					sessionStorage.flightStatus = true;
				}
				catch (err) {
				}
			}
			$scope.createLegs = function (howMany) {
				haCustomerSelections.createLegs(howMany, false);
			};

			//$scope.$emit('$haFlightStatusReady');

			//$scope.depart = 'today';
			//Flight Search by results by flight number
			setTimeout(function () {
				angular.element('#DepartureDate').click();

			}, 1000);


			$scope.FlightDelayStatus = {
				latedeparture: 'Late Departure',
				latearrival: 'Late Arrival',
				delayed: 'Delayed',
				cancelled: 'Cancelled'
			};

			$scope.FlightOnTimeStatus = {
				ontime: 'on Time',
				earlyarrival: 'Early Arrival'
			};

			$scope.departureDateToday = (typeof serverShortDate !== 'undefined') ? new Date(serverShortDate) : new Date();
			$scope.departureDateTomorrow = (typeof serverShortDate !== 'undefined') ? new Date(serverShortDate) : new Date();
			$scope.departureDateYesterday = (typeof serverShortDate !== 'undefined') ? new Date(serverShortDate) : new Date();
			$scope.departureDateTomorrow.setDate($scope.departureDateToday.getDate() + 1);
			$scope.departureDateYesterday.setDate($scope.departureDateToday.getDate() - 1);

			var date = new Date();
			$scope.DepartureDate = [date.getMonth() + 1, date.getDate(), date.getFullYear()].join('/');

			haGlobals('jsonFlightResultModel', function (jsonFlightResultModel) {
				$scope.OperatedBy = jsonFlightResultModel.OperatedBy;
				$scope.FlightNo = jsonFlightResultModel.FlightNumber;
				$scope.Origin = jsonFlightResultModel.Origin;
				$scope.Destination = jsonFlightResultModel.Destination;
				$scope.FlightStatus = jsonFlightResultModel.FlightStatus;
				$scope.AircraftType = jsonFlightResultModel.AircraftType;

				$scope.EquipmentType = jsonFlightResultModel.EquipmentType;

				if (jsonFlightResultModel.FlightStatus === 'Delayed') {
					$scope.FlightStatusStyle = 'delayed';
				} else if (
					jsonFlightResultModel.FlightStatus === 'Arrived' ||
					jsonFlightResultModel.FlightStatus === 'Landed') {
					$scope.FlightStatusStyle = 'arrived';
				} else if (jsonFlightResultModel.FlightStatus === 'Scheduled' ||
					jsonFlightResultModel.FlightStatus === 'Delayed' ||
					jsonFlightResultModel.FlightStatus === 'Cancelled') {
					$scope.FlightStatusStyle = 'scheduled';
				} else if (jsonFlightResultModel.FlightStatus === 'Recovery' ||
					jsonFlightResultModel.FlightStatus === 'Diverted' ||
					jsonFlightResultModel.FlightStatus === 'In Air' ||
					jsonFlightResultModel.FlightStatus === 'Expected' ||
					jsonFlightResultModel.FlightStatus === 'Departed') {
					$scope.FlightStatusStyle = 'active';
				}

				//$scope.FlightStatus = 'arrived'; //arrived, active, delayed, scheduled

				if (jsonFlightResultModel.FlightOnTimeStatus != null && jsonFlightResultModel.FlightOnTimeStatus !== '') {
					$scope.FlightStatusIndicator = jsonFlightResultModel.FlightOnTimeStatus;
					if ($scope.FlightStatusIndicator in $scope.FlightDelayStatus) {
						$scope.OnTimeStatusStyle = 'delayed'; //ontime, delayed
					} else if ($scope.FlightStatusIndicator in $scope.FlightOnTimeStatus) {
						$scope.OnTimeStatusStyle = 'ontime'; //ontime, delayed
					}
				}

				$scope.ScheduledDepartureTime = jsonFlightResultModel.ScheduledDepartureTime;
				$scope.ScheduledArrivalTime = jsonFlightResultModel.ScheduledArrivalTime;
				$scope.ActualDepartureTime = jsonFlightResultModel.ActualDepartureTime;
				$scope.EstimatedArrivalTime = jsonFlightResultModel.EstimatedArrivalTime;
				$scope.ScheduledDepartureDate = jsonFlightResultModel.ScheduledDepartureDate;
				$scope.ScheduledArrivalDate = jsonFlightResultModel.ScheduledArrivalDate;
				$scope.ActualDepartureDate = jsonFlightResultModel.ActualDepartureDate;
				$scope.EstimatedArrivalDate = jsonFlightResultModel.EstimatedArrivalDate;

				$scope.ScheduledDepartureTerminal = jsonFlightResultModel.ScheduledDepartureTerminal;
				$scope.ScheduledDepartureGate = jsonFlightResultModel.ScheduledDepartureGate;
				$scope.ScheduledArrivalGate = jsonFlightResultModel.ScheduledArrivalGate;
				$scope.ActualArrivalTerminal = jsonFlightResultModel.ActualArrivalTerminal;
				$scope.MapURL = jsonFlightResultModel.MapURL;

				if (jsonFlightResultModel.FlightStatus === 'In Air') {
					$scope.MapHideStyle = 'map-hidden';
				} else {
					$scope.MapHideStyle = '';
				}

				if (jsonFlightResultModel.FlightDelayDuration == null || jsonFlightResultModel.FlightDelayDuration === '00:00') {
					$scope.FlightDelayDuration = '';
				} else {
					$scope.FlightDelayDuration = jsonFlightResultModel.FlightDelayDuration;
				}
			});
		};

		HaFlightStatusController.$inject = ['$scope', 'haCustomerSelections'];

		var HaFlightStatusLink = function ($scope, $el) {

			haGlobals('jsonFSByRouteModel', function (jsonFSByRouteModel) {
				$scope.flightsByRouteVM = jsonFSByRouteModel;
			});

			$scope.$index = 0;

			$scope.$arrived = $el.find('.arrived');
			$scope.$active = $el.find('.active');
			$scope.$scheduled = $el.find('.scheduled');

			$scope.$arrivedExpandControl = $el.find('.arrived-expand-control');
			$scope.$activeExpandControl = $el.find('.active-expand-control');
			$scope.$scheduledExpandControl = $el.find('.scheduled-expand-control');

			$scope.arrivedIsExpanded = false;
			$scope.activeIsExpanded = false;
			$scope.scheduledIsExpanded = false;

			$scope.mapIsHidden = true;

			$scope.convertToDate = function (dateString) {
				return new Date(dateString);
			};

			$scope.expandBtnClick = function (section) {
				if (section === 'arrived') {
					$scope.expandArrived($scope.arriveIsExpanded);
				}
				else if (section === 'active') {
					$scope.expandActive($scope.activeIsExpanded);
				}
				else if (section === 'scheduled') {
					$scope.expandScheduled($scope.scheduledIsExpanded);
				}
			};

			$scope.expandArrived = function (expanded) {
				if (!expanded) {
					$scope.openArrived();
				}
				else {
					$scope.closeArrived();
				}
			};

			$scope.openArrived = function () {
				$scope.arriveIsExpanded = true;
				$scope.$arrived.addClass('expand');
				$scope.$arrivedExpandControl.removeClass('fontIcon20-circlePlus');
				$scope.$arrivedExpandControl.addClass('fontIcon20-circleMinus');
			};

			$scope.closeArrived = function () {
				$scope.arriveIsExpanded = false;
				$scope.$arrived.removeClass('expand');
				$scope.$arrivedExpandControl.removeClass('fontIcon20-circleMinus');
				$scope.$arrivedExpandControl.addClass('fontIcon20-circlePlus');
			};

			$scope.expandActive = function (expanded) {
				if (!expanded) {
					$scope.openActive();
				}
				else {
					$scope.closeActive();
				}
			};

			$scope.openActive = function () {
				$scope.activeIsExpanded = true;
				$scope.$active.addClass('expand');
				$scope.$activeExpandControl.removeClass('fontIcon20-circlePlus');
				$scope.$activeExpandControl.addClass('fontIcon20-circleMinus');
			};

			$scope.closeActive = function () {
				$scope.activeIsExpanded = false;
				$scope.$active.removeClass('expand');
				$scope.$activeExpandControl.removeClass('fontIcon20-circleMinus');
				$scope.$activeExpandControl.addClass('fontIcon20-circlePlus');
			};

			$scope.expandScheduled = function (expanded) {
				if (!expanded) {
					$scope.openScheduled();
				}
				else {
					$scope.closeScheduled();
				}
			};

			$scope.openScheduled = function () {
				$scope.scheduledIsExpanded = true;
				$scope.$scheduled.addClass('expand');
				$scope.$scheduledExpandControl.removeClass('fontIcon20-circlePlus');
				$scope.$scheduledExpandControl.addClass('fontIcon20-circleMinus');
			};

			$scope.closeScheduled = function () {
				$scope.scheduledIsExpanded = false;
				$scope.$scheduled.removeClass('expand');
				$scope.$scheduledExpandControl.removeClass('fontIcon20-circleMinus');
				$scope.$scheduledExpandControl.addClass('fontIcon20-circlePlus');
			};

			$scope.mapToggleBtnClick = function (mapIndex) {

				$scope.$currentMapToggle = $el.find('.map-toggle-' + mapIndex);
				$scope.$currentMap = $el.find('.flight-status-map-' + mapIndex);

				if ($scope.$currentMapToggle.hasClass('map-hidden')) {
					$scope.$currentMapToggle.removeClass('map-hidden');
					$scope.$currentMapToggle.addClass('map-shown');
					$scope.$currentMap.addClass('expand');
				}
				else {
					$scope.$currentMapToggle.removeClass('map-shown');
					$scope.$currentMapToggle.addClass('map-hidden');
					$scope.$currentMap.removeClass('expand');
				}
			};

			$scope.createLegs(2);
			//$scope.depart = 'today';

			$scope.openOnLoad = function () {
				if ($scope.$active !== null && $scope.$active !== undefined && $scope.flightsByRouteVM !== undefined && $scope.flightsByRouteVM.ArrivedFlights.length > 0) {
					$scope.openArrived();
				}
				else if ($scope.$active !== null && $scope.$active !== undefined && $scope.flightsByRouteVM !== undefined && $scope.flightsByRouteVM.ActiveFlights.length > 0) {
					$scope.openActive();
				}
				else if ($scope.$scheduled !== null && $scope.$scheduled !== undefined && $scope.flightsByRouteVM !== undefined && $scope.flightsByRouteVM.ScheduledFlights.length > 0) {
					$scope.openScheduled();
				}
			};

			$scope.$emit('$haFlightStatusReady');

			$scope.openOnLoad();

		};

		return {
			restrict: 'A',
			scope: true,
			link: HaFlightStatusLink,
			controller: HaFlightStatusController
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Checkout
	// --------------------------------------------
	//
	// * **Class:** HaCheckout
	// * **Author:** Cory Shaw
	//
	// This is the main module for the review and pay page in the booking path

	'use strict';

	var module = angular.module('haCustomDirectivesModule', ['haUserProfileAPI']);

	module.directive('numberOnly', function () {
		return {
			require: 'ngModel',
			link: function (scope, element, attrs, modelCtrl) {
				modelCtrl.$parsers.push(function (inputValue) {
					if (inputValue == null) {
						return '';
					}
					var transformedInput = inputValue.replace(/[^0-9]/g, '');
					if (transformedInput !== inputValue) {
						var el = element[0];
						var start = 0;
						var end = 0;
						var isError = false;
						try {
							start = el.selectionStart;
							end = el.selectionEnd + transformedInput.length - inputValue.length;
						}
						catch (ex) {
							isError = true;
						}

						modelCtrl.$setViewValue(transformedInput);
						modelCtrl.$render();

						if (!isError) {
							try {
								el.setSelectionRange(start, end);
							}
							catch (ex) {
							}
						}
					}
					return transformedInput;
				});
			}
		};
	});

	module.directive('minLength', function () {
		return {
			require: 'ngModel',
			link: function (scope, elm, attr, ngModel) {

				var minlength = parseInt(attr.minLength);

				scope.minLengthValidator = function (value) {
					var validity = ngModel.$isEmpty(value) || value.length >= minlength;
					ngModel.$setValidity('minlength', validity);
					return validity ? value : undefined;
				};

				attr.$observe('minLength', function (val) {
					minlength = parseInt(val);
					scope.minLengthValidator(ngModel.$viewValue);
				});

				scope.$watch(attr.ngModel, function (newValue) {
					minlength = parseInt(attr.minLength);
					scope.minLengthValidator(newValue);
				});

				//  ngModel.$parsers.push(minLengthValidator);
				//      ngModel.$formatters.push(minLengthValidator);
			}
		};
	});

	module.directive('maxLength', function () {
		return {
			require: 'ngModel',
			link: function (scope, elm, attr, ngModel) {

				var maxlength = parseInt(attr.maxLength);

				scope.maxLengthValidator = function (value) {
					var validity = ngModel.$isEmpty(value) || value.length <= maxlength;
					ngModel.$setValidity('maxlength', validity);
					return validity ? value : undefined;
				};

				attr.$observe('maxLength', function (val) {
					maxlength = parseInt(val);
					elm.attr('maxlength', maxlength);
					scope.maxLengthValidator(ngModel.$viewValue);
				});


				scope.$watch(attr.ngModel, function (newValue) {
					maxlength = parseInt(attr.maxLength);
					scope.maxLengthValidator(newValue);
				});

				//ngModel.$parsers.push(maxLengthValidator);
				//     ngModel.$formatters.push(maxLengthValidator);
			}
		};
	});

	module.directive('regexPattern', function () {
		return {
			require: 'ngModel',
			link: function (scope, elm, attr, ngModel) {

				var regexPattern = new RegExp(attr.regexPattern);


				ngModel.$parsers.push(function (inputValue) {
					if (inputValue == null) {
						return '';
					}
					var transformedInput = inputValue.replace(regexPattern, '');
					if (transformedInput !== inputValue) {
						var el = elm[0];
						var start = 0;
						var end = 0;
						var isError = false;
						try {
							start = el.selectionStart;
							end = el.selectionEnd + transformedInput.length - inputValue.length;
						}
						catch (ex) {
							isError = true;
						}

						ngModel.$setViewValue(transformedInput);
						ngModel.$render();

						if (!isError) {
							try {
								el.setSelectionRange(start, end);
							}
							catch (ex) {
							}
						}
					}
					return transformedInput;
				});

			}
		};
	});

	module.directive('userNameUnique', ['$q', 'haUserProfileAPI', function ($q, haUserProfileAPI) {
		return {
			require: 'ngModel',
			link: function (scope, elem, attrs, ctrl) {
				var canceler;
				scope.safeApply = function (fn) {
					var phase = this.$root.$$phase;
					if (phase === '$apply' || phase === '$digest') {
						if (fn && (typeof (fn) === 'function')) {
							fn();
						}
					} else {
						this.$apply(fn);
					}
				};

				scope.$watch(attrs.ngModel, function () {
					scope.safeApply(function () {
						ctrl.$setValidity('userNameNotUnique', true);
					});
				});
				elem.on('blur', function (/* evt */) {
					if (!scope.isValid()) {
						scope.$apply(function () {
							ctrl.$setValidity('userNameAjax', false);
							var data = {'username': elem.val()};

							if (canceler) {
								canceler.resolve();
							}
							canceler = $q.defer();

							haUserProfileAPI.checkUsernameAvailability(data, canceler)
							.success(function (data) {
								ctrl.$setValidity('userNameNotUnique', data.IsSuccess);
								ctrl.$setValidity('userNameAjax', true);
							});
						});
					}
				});

				scope.isValid = function () {
					if (elem.controller('ngModel').$error) {
						var isError = false;
						var obj = elem.controller('ngModel').$error;
						for (var prop in obj) {
							if (obj.hasOwnProperty(prop)) {
								if (prop !== 'userNameAjax' && prop !== 'userNameNotUnique' && obj[prop] === true) {
									isError = true;
									break;
								}
							}
						}
						return isError;
					}
					else {
						return false;
					}
				};
			}
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Booking Hero
	// --------------------------------------------
	//
	// * **Class:** HaBookingHero
	// * **Author:** Josh Nielsen
	//
	// Hero banner that displays current booking progress

	'use strict';

	var module = angular.module('haBookingHeroModule', []);

	module.directive('haBookingHero', function () {

		var HaBookingHeroController = function ($scope) {
			$scope.$emit('$haBookingHeroReady');
		};

		HaBookingHeroController.$inject = ['$scope'];

		var HaBookingHeroLink = function ($scope) {

			$scope.exampleMethod = function () {
				return $scope;
			};
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaBookingHeroLink,
			controller: HaBookingHeroController
		};
	});

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haGlobalFooterModule', []).directive('haGlobalFooter', ['$rootScope', function ($rootScope) {
		return {
			restrict: 'A',
			scope: true,
			link: function () {
				$('.back-to-top').click(function () {
					//Desktop back to top link and focus skip to content for screens reader
					if(!$rootScope.isMobile){
						window.scrollTo({ top: 0, behavior: 'smooth' });
						$('#skipToContent').focus();
					}

					//Mobile back to top link
					if($rootScope.isMobile){
						window.scrollTo(0, 0);
					}
				});
			},
			controller: ['$scope', function ($scope) {

				// notion of "footer errors" is currently unused
				function getUrlVars() {

					var vars = [];
					var hash;
					var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
					for (var i = 0; i < hashes.length; i += 1) {
						hash = hashes[i].split('=');
						vars.push(hash[0]);
						vars[hash[0]] = hash[1];
					}
					return vars;
				}

				var vars = getUrlVars();

				if (vars.subscriptionflag && vars.subscriptionflag === '1') {
					$scope.ShowFooterHeaderAlert = true;
					$scope.FooterErrorType = 'error';
					$scope.FooterHeaderMessage = 'Error Occured';
				}

				$scope.Date = new Date();
			}]
		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	var module;
	try {
		module = angular.module('haFormNativeappLinkSmsModule');
	} catch (e) {
		module = angular.module('haFormNativeappLinkSmsModule', ['haHttpService', 'haGeoDataModule']);
	}

	module.directive('haFormNativeappLinkSms', [
		'$q',
		'haGlobals',
		'haHttpService',
		'haGeoDataSvc',

		function ($q, haGlobals, http, geo) {
			var link = function ($scope, el, attrs) {

				$scope.IsCountryDataReady = false;

				// Fetch list of Countries
				geo.getCountries().then(
					function (data) { // success
						$scope.countries = data;
						angular.extend($scope, {
							countryCodeData: geo.getPhoneCountryCodes()
						});

						// get phoneRegex by CountryCode
						// This should be called after countries data set in cache
						$scope.phoneRegexByCC = function (cc) {
							var country = geo.lookupCountryByCode(cc);
							if (country != null) {
								return geo.getPhoneNumberRegex(country.Key);
							}
							return /.*/;
						};

						$scope.phoneCountryCodeByCC = function (cc) {
							var countryByCC = $scope.countries.filter(function (country) {
								if (country.IsoCode === cc) {
									return country;
								}
							});

							if (countryByCC.length === 1) {
								return countryByCC[0].PhoneCountryCode;
							}
							return '';
						};

						$scope.IsCountryDataReady = true;
					},
					function () { // failure
						// failed in loading countries data. hide the form
						if (!!$scope.form) {
							$($scope.form).hide();
						}
					}
				);

				$(document).ready(function () {
					if (!$scope.isModal) {
						$scope.form = $('#' + attrs.formName);
						$scope.msgSent = el.find('.msgSent');
						$scope.msgFailed = el.find('.msgFailed');
					}
				});

				// init recipient
				$scope.recipient = {};
				$scope.recipient.CountryCode = "USA"; // set default to USA
				haGlobals('defaultCountryCode', function (code) {
					$scope.recipient.CountryCode = code;
				});
				$scope.recipient.Number = "";

				$scope.formNativeappLinkSmsSubmit = function () {
					if ($scope.isModal && !$scope.form) {
						$scope.form = $('#' + attrs.formName);
						$scope.msgSent = el.find('.msgSentModal');
						$scope.msgFailed = el.find('.msgFailedModal');
					}
					$scope.sendSmsRequest()
						.success(function () {
							$($scope.msgSent).fadeIn("slow");
							setTimeout(function () { $($scope.msgSent).fadeOut("slow"); }, 5000);

							$scope.isSubmitted = false;

						}).error(function () {
							$($scope.msgFailed).fadeIn("slow");
							setTimeout(function () { $($scope.msgFailed).fadeOut("slow"); }, 5000);

							$scope.isSubmitted = false;
						});
				};

				$scope.isSubmitted = false;

				$scope.sendSmsRequest = function () {
					// loading spinner
					$scope.isSubmitted = true;
					var elements = $scope.form[0].elements;
					var phone = '+' + $(elements.PhoneCountryCode).val() + $(elements.PhoneNumber).val().replace(/\D/g,'');
					var area = $(elements.area).val();
					return http.POST('/api/v2/shared/TextAppLink', { phone: phone, area: area });
				};
			};

			return {
				restrict: 'A',
				scope: true,
				link: link
			};
		}]
	);
})(angular);

;
(function (angular) {

	// Explore Map
	// --------------------------------------------
	//
	// * **Class:** ExploreMap
	// * **Author:** Cory Shaw
	//
	// This is the interactive map that appears in the explore section

	'use strict';

	var module = angular.module('exploreMapModule', []);

	module.directive('exploreMap', ['haConfig', function (haConfig) {

		var ExploreMapController = function () {

		};

		ExploreMapController.$inject = ['$scope'];

		var ExploreMapLink = function ($scope) {
			$scope.$emit('$exploreMapReady');
		};

		ExploreMapLink.$inject = ['$scope'];

		return {
			restrict: 'A',
			templateUrl: haConfig.getTemplateUrl('explore-map-base-template'),
			scope: true,
			link: ExploreMapLink,
			controller: ExploreMapController
		};
	}]);

})(angular);;
(function (angular) {

	// Explore Hero
	// --------------------------------------------
	//
	// * **Class:** ExploreHero
	// * **Author:** Cory Shaw
	//
	// This is the hero full bleed image that appears at the top of many pages in the explore area

	'use strict';

	var module = angular.module('exploreHeroModule', []);

	module.directive('exploreHero', ['haConfig', function (haConfig) {

		var ExploreHeroController = function ($scope) {
			$scope.$emit('$methodsBound');
		};

		ExploreHeroController.$inject = ['$scope'];

		var ExploreHeroLink = function ($scope, $el, $attrs) {
			$scope.PhotoGallery = [];
			// NEP: FIXME: Over-assigment with an undefined or global.
			// $scope.PhotoGallery = PhotoGalleryVM;

			$scope.widgetTitle = $attrs.widgettitle;
			$scope.widgetDescription = $attrs.widgetdescription;
			$scope.widgetLinkLabel = $attrs.widgetlinklabel;
		};

		return {
			restrict: 'A',
			scope: true,
			link: ExploreHeroLink,
			templateUrl: haConfig.getTemplateUrl('explore-hero-base-template.html'),
			controller: ExploreHeroController
		};
	}]);

})(angular);
;
(function (angular) {
	'use strict';
	angular.module('haGlobalHeaderModule', ['haGlobalHeaderAPI'])
		.directive('haGlobalHeader', ['$timeout', 'haConfig', 'haModal', '$http', '$rootScope', function ($timeout, haConfig, haModal, $http, $rootScope) {
		return {
			restrict: 'A',
			scope: true,
			link: function (scope, elem) {

				var tray = elem.find('[ha-primary-nav-tray]');
				var navTrayHeightPx = '185px';

				function toggleNavigationTray(open, section) {
					if (open) {
						tray.css({ 'bottom': 0, 'visibility': 'visible' });
						$('.navtray-content-inner.active').removeClass('active');
						var active = $('.navtray-content-inner--' + section);
						$('.nav-overflow').css('max-height', navTrayHeightPx);
						scope.primaryNavtrayIsOpen = true;
						scope.currentSection = section;
						
						$timeout(function () {
							active.addClass('active');
							$('.nav-pane').css('opacity', '1');
						}, 120);

					} else {
						tray.css({ 'bottom': 0, 'transition': 'bottom .2s ease-in' });
						scope.primaryNavtrayIsOpen = false;
						scope.currentSection = null;
						$('.nav-overflow').css('max-height', '0');
						$('.nav-pane').css('opacity', '0');

						$timeout(function () {
							tray.css({ 'visibility': 'hidden' });
						}, 200);
					}
				}

				function toggleRegionMenu(open) {
					scope.regionSelectMenuIsOpen = open;

					if (!open) {
						$('.region.link').focus();
					}
				}

				function transitionTrayContent(section) {

					var current = $('.nav-pane.' + scope.currentSection);
					var to = $('.nav-pane.' + section);

					current.css('opacity', '1');
					scope.currentSection = section;

					$timeout(function () {
						to.css('opacity', '1');
					}, 90);
				}

				scope.skipToContent = function () {
					$('[role="main"] :tabbable:first').focus();
				};

				scope.toggleNavigation = function (section) {
					if (scope.primaryNavtrayIsOpen) {
						if (scope.currentSection === section) {
							toggleNavigationTray(false);
						} else {
							transitionTrayContent(section);
						}
					} else {
						toggleNavigationTray(true, section);
					}
					// Handle hiding Expert Booking tile on frontend side since tiles are cached per user in Sitecore
					// so it didn't work well when using Sitecore rule to show/hide the tile
					if (section === 'book') {
						if (!$rootScope.user.isExpertBookingOptIn) {
							$timeout(function() {
								angular.element('[ha-global-header]').find('a[href^="/Book/ExpertBooking"]').addClass('ng-hide');
							}, 0);
						}
					}
				};

				scope.toggleRegion = function () {
					if (scope.regionSelectMenuIsOpen) {
						toggleRegionMenu(false);
					} else {
						toggleRegionMenu(true);
					}
				};

				if (!scope.$root.isMobile) {
					$('html')
						.on('click',
							function(e) {

								var navTray = $.grep($(e.target).parents(),
									(function(n) {
										return $(n).hasClass('nav-overflow');
									}))
									.length >
									0;

								if (scope.primaryNavtrayIsOpen && !navTray && !$(e.target).hasClass('ha-nav')) {
									$timeout(function() {
										toggleNavigationTray(false);
									});
								}

								if (scope.regionSelectMenuIsOpen && !$(e.target).closest('.parent').hasClass('region')) {
									$timeout(function() {
										toggleRegionMenu(false);
									});
								}
							});

					$('body')
						.delegate('a:not(.ha-nav.nav-li-inner, .nav-pane a)',
							'focus',
							function() {
								if (scope.primaryNavtrayIsOpen) {
									$timeout(function() {
										toggleNavigationTray(false);
									});
								}
							})
						.delegate('a:not(.region)',
							'focus',
							function() {
								if (scope.regionSelectMenuIsOpen) {
									$timeout(function() {
										toggleRegionMenu(false);
									});
								}
							});

					$('body')
						.on('keyup',
							function(e) { // -- escape key
								if (e.keyCode === 27) {
									e.preventDefault();
									if (scope.regionSelectMenuIsOpen) {
										$timeout(function() {
											toggleRegionMenu(false);
										});
									}
									if (scope.primaryNavtrayIsOpen) {

										var pane = $(document.activeElement).parent().attr('class').split(' ')[1];

										if (['book', 'manage', 'airline', 'island'].indexOf(pane) > -1) {
											$('.ha-nav.' + pane).focus();
										}
										$timeout(function() {
											toggleNavigationTray(false);
										});
									}
								}
							});

					$('#skipToContent')
						.focus(function() {
							$(this).removeClass('sr-only');
						})
						.blur(function() {
							$(this).addClass('sr-only');
						});

					$('.nav-pane').removeClass('init');
				} else {

					scope.$watch('mobileMenus',	function(newVal) {
						if (newVal.menuOpen || newVal.myAcctOpen) {
							$('.ha-global-footer, section[role="main"]').hide();
						} else {
							$('.ha-global-footer, section[role="main"]').show();
						}
					}, true);

					// elem.on('remove', function() {
					// 	$('.ha-global-footer, section[role="main"]').show();
					// });
				}
			},
			controller: ['$scope', '$rootScope', 'haUser', 'haFavorites', 'haGlobals', 'haGlobalHeaderAPI', function ($scope, $rootScope, haUser, haFavorites, haGlobals, haGlobalHeaderAPI) {

				if ($rootScope.user) {

					$.extend($rootScope.user, {
						accountType: sessionStorage.getItem('accType'),
						haMiles: sessionStorage.getItem('haMiles')
					});
				}

				$scope.mobileMenus = {
					myAcctOpen: false,
					menuOpen: false
				};
				var redirectFn = function () {
					if ($rootScope.isMobile) {
						window.location.href = $scope.link;
					}
					else {
						window.open(
							$scope.link,
							'_blank'
						);
					}
				}
				$rootScope.barclaysPopup = function (link, sccontent) {
					$scope.link = link;
					if ($rootScope.isMobile) {
						Promise.all([
							$scs.get(sccontent + '.Content'),
							$scs.get(sccontent + '.Title')
						])
							.then(function (strings) {
								haModal(haConfig.getTemplateUrl('ha-modal.html'), {
									backdrop: 'true',
									title: strings[1], // don't show the header (we don't want the H1)
									success: {
										label: "Okay", fn: redirectFn
									},
									cancel: {}, // hide the cancel button
									size: 'modal-md',
									defaultContent: strings[0],
									scope: $scope,
									mobileVerticalCenter: true
								});
							});
					}
					else {
						redirectFn();
					}
				}
				function readCookie(name) {
					name += '=';
					for (var ca = document.cookie.split(/;\s*/), i = ca.length - 1; i >= 0; i--) {
						if (!ca[i].indexOf(name)) {
							return ca[i].replace(name, '');
						}
					}
				};

				function getBccIPSCookie() {
					var checkCookie = readCookie('IPS');
					if (checkCookie !== undefined) {
						return checkCookie;
					}
					return null;
				};

				// Define corporate and individual account types on rootscope
				$rootScope.corpAccTypes = ['C', 'R', 'W', 'A'];
				$rootScope.individualAccTypes = ['B', 'D', 'E', 'H', 'I', 'O', 'P', 'S', 'T', 'V', 'Z'];

				$scope.logout = function () {
					haUser.logout();
					sessionStorage.clear();
				};

				var enableUSWebSite;
				var enableAUWebSite;
				var enableNZWebSite;
				var sitecoreContextId;

				haUser.updateUser();
				haFavorites.updateFavorites();

				haGlobals('enableUSSite', function (enableUSSite) {
					enableUSWebSite = enableUSSite === "True";
				});
				haGlobals('enableAUSite', function (enableAUSite) {
					enableAUWebSite = enableAUSite === "True";
				});
				haGlobals('enableNZSite', function (enableNZSite) {
					enableNZWebSite = enableNZSite === "True";
				});
				haGlobals('sitecoreContextId', function (sitecoreCtxtId) {
					sitecoreContextId = sitecoreCtxtId;
				});
				haGlobals(['isLoggedIn', 'acctType', 'acctNo', 'isExpertBookingOptIn','haMilesEliteStatus'], function (isLoggedIn, acctType, acctNo, isExpertBookingOptIn, haMilesEliteStatus) {
					$rootScope.isLoggedIn = isLoggedIn;
					$rootScope.bccIpsOffer = getBccIPSCookie() === "True";
					if ($rootScope.user) {
						$.extend($rootScope.user, { accountType: acctType, haMiles: acctNo, isExpertBookingOptIn: isExpertBookingOptIn,  haMilesEliteStatus: haMilesEliteStatus });
					}
				});

				$scope.navSelectCountry = function (country) {
					haGlobalHeaderAPI.selectCountry(country, sitecoreContextId).success(function (result) {
						if (result !== null && result !== '') {
							window.location.href = result.RedirectUrl;
						}
					}, function (errorMessage) {
						window.alert(errorMessage);
					});					
				};
			}]
		};
	}]);
})(angular);
;
(function (angular) {
	'use strict';
	angular.module('haHeaderSearchModule', [])
	.directive('haHeaderSearch', ['haModal', '$http', function (haModal, $http) {
		return {
			restrict: 'A',
			scope: true,
			link: function (scope) {

				function toggleSearchModal(open) {

					if (open) {
						$('#searchModalTemplate .search-textarea').removeAttr('id');

						haModal({
							id: 'SearchModel',
							backdrop: false,
							scope: scope,
							size: 'modal-size',
							template: $('#searchModalTemplate'),
							cancel: {
								fn: function () {
									scope.searchModalIsOpen = false;
								}
							}
						});

						$('#searchModalTemplate .search-textarea').attr('id', 'help-search');
						$('#searchModalTemplate').attr('role', 'dialog');

						// ha-modal-service.js:107 focuses input

					} else {
						scope.$modalCancel();
						$('.nav-utility-li--search > a').focus();
					}

					scope.searchModalIsOpen = open;
				}

				scope.search = function () {
					if (scope.searchText) {
						$('.search-submit-btn').removeClass('disabled');
						$('.search-submit-btn > .sr-only').css('display', 'none');
					} else {
						$('.search-submit-btn').addClass('disabled');
						$('.search-submit-btn > .sr-only').css('display', 'block');
					}
				};

				scope.submitSearch = function () {

					if (!scope.searchText) {
						return false; // display error mayhaps?
					}

					$http({
						url: '/search-results',
						method: 'POST',
						headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
						data: $.param({ 'searchText': scope.searchText })
					}).then(function () {
						window.location.href = '/search-results';
					});
				};

				scope.toggleSearch = function () {
					if (!scope.searchModalIsOpen) {
						toggleSearchModal(true);
					} else {
						toggleSearchModal(false);
					}
				};
			}
		};
	}]);
})(angular);
;
(function (angular) {
	// Ha Cms Body Copy With Sidebar
	// --------------------------------------------
	//
	// * **Class:** HaCmsBodyCopyWithSidebar
	// * **Author:** Chad Kumabe

	'use strict';

	var mod = angular.module('haCmsBodyCopyWithSidebarModule', []);

	mod.directive('haCmsBodyCopyWithSidebar', function () {

		var HaCmsBodyCopyWithSidebarController = function ($scope) {
			$scope.$emit('$haCmsBodyCopyWithSidebarReady');
		};

		HaCmsBodyCopyWithSidebarController.$inject = ['$scope'];

		var HaCmsBodyCopyWithSidebarLink = function ($scope, $el) {
			// polyfill needed for Ie8
			if ($('html').hasClass('lte-ie8')) {
				$el.find(':last-child').addClass('last-child');
			}
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaCmsBodyCopyWithSidebarLink,
			controller: HaCmsBodyCopyWithSidebarController
		};
	});

})(angular);
;
(function (angular) {

	'use strict';

	var el;

	function setText(id, text) {
		if (el[id]) {
			el[id].textContent = text;
		}
	}

	var module = angular.module('haPerformanceStatsModule', []);

	module.directive('haPerformanceStats', [
		'$log',
		'$window',
		'$document',
		'$rootScope',
		'haConfig',

		function ($log, $window, $document, $rootScope, haConfig) {
			// globals
			var performance = $window.performance;

			var ngStart = (performance != null) ? performance.now() : 0;
			var stats = $window.perfStats;

			stats.timeToAngular = (ngStart - stats.headStart);

			var countScopesWatchers = function () {
				// This logic is borrowed from $digest(). Keep it in sync!
				var next;
				var current;
				var target = $rootScope;
				var scopes = 0;
				var watchers = 0;

				current = target;
				do {
					scopes += 1;

					if (current.$$watchers) {
						watchers += current.$$watchers.length;
					}

					// Insanity Warning: scope depth-first traversal
					// yes, this code is a bit crazy, but it works and we have tests to prove it!
					// this piece should be kept in sync with the traversal in $broadcast
					if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
						while (current !== target && !(next = current.$$nextSibling)) {
							current = current.$parent;
						}
					}
				} while ((current = next));

				return [scopes, watchers];
			};

			return {
				restrict: 'A',
				replace: true,
				templateUrl: haConfig.getTemplateUrl('ha-performance-stats.html'),
				link: function (/* scope, elem, attrs */) {
					// Cache DOM elements
					el = {
						'#scopes': $document[0].querySelector('#scopes'),
						'#watchers': $document[0].querySelector('#watchers'),
						'#dirty-checks': $document[0].querySelector('#dirty-checks'),
						'#digest-cycles': $document[0].querySelector('#digest-cycles'),
						'#digest-ms': $document[0].querySelector('#digest-ms'),
						'#digest-fps': $document[0].querySelector('#digest-fps'),
						'#avg-digest-ms': $document[0].querySelector('#avg-digest-ms'),
						'#avg-digest-fps': $document[0].querySelector('#avg-digest-fps'),
						'#max-digest-ms': $document[0].querySelector('#max-digest-ms'),
						'#max-digest-fps': $document[0].querySelector('#max-digest-fps'),

						'#head-load': $document[0].querySelector('#head-load'),
						'#body-load': $document[0].querySelector('#body-load'),
						'#footer-load': $document[0].querySelector('#footer-load'),
						'#vendor-load': $document[0].querySelector('#vendor-load'),
						'#app-load': $document[0].querySelector('#app-load'),
						'#metrics-load': $document[0].querySelector('#metrics-load'),
						'#time-to-eop': $document[0].querySelector('#time-to-eop'),
						'#time-to-ng': $document[0].querySelector('#time-to-ng')
					};

					setText('#head-load', stats.headLoad.toFixed(1));
					setText('#body-load', stats.bodyLoad.toFixed(1));
					setText('#footer-load', stats.footerLoad.toFixed(1));
					setText('#vendor-load', stats.vendorScriptLoad.toFixed(1));
					setText('#app-load', stats.appLoad.toFixed(1));
					setText('#metrics-load', stats.metricsLoad.toFixed(1));
					setText('#time-to-eop', stats.TTLB.toFixed(1));
					setText('#time-to-ng', stats.timeToAngular.toFixed(1));

					// If the browser doesn't support Web Performance API
					// (I'm looking at you, Safari), don't even try.
					if (performance != null) {
						var digestCycles = 0;
						var digestStart = 0;
						var sumDigestMs = 0;
						var maxDigestMs = 0;
						var dirtyChecks = 0;

						// $digest loop uses a reverse while.
						// Pushing onto the end of $$watchers array makes this run first...
						$rootScope.$$watchers.push({
							eq: false,
							last: null,
							fn: function () {
							},
							exp: function () {
							},
							get: function () {
								dirtyChecks++;

								// Only update digestStart if not set. This allows for multiple
								// iterations inside the "dirty loop."
								//
								// NOTE: This technique for timing the $digest cycles
								//       DOES NOT capture time spent processing the asyncQueue!
								if (digestStart === 0) {
									// $log.debug('$rootScope.$watch: digestStart');
									digestStart = performance.now();
									digestCycles++;
								}

								// Schedules a one-shot callback after digest loop is clean
								$rootScope.$$postDigest(function () {
									if (digestStart !== 0) {
										var digestEnd = performance.now();
										var digestMs = (digestEnd - digestStart);
										setText('#digest-ms', digestMs.toFixed(1));
										setText('#digest-fps', (1000 / digestMs).toFixed(0));

										maxDigestMs = Math.max(digestMs, maxDigestMs);
										setText('#max-digest-ms', maxDigestMs.toFixed(1));
										setText('#max-digest-fps', (1000 / maxDigestMs).toFixed(0));

										sumDigestMs += digestMs;
										if (digestCycles > 0) {
											var avgDigestMs = sumDigestMs / digestCycles;
											setText('#avg-digest-ms', avgDigestMs.toFixed(1));
											setText('#avg-digest-fps', (1000 / avgDigestMs).toFixed(0));
										}

										setText('#dirty-checks', dirtyChecks);
										setText('#digest-cycles', digestCycles);

										var count = countScopesWatchers();
										var scopes = count[0];
										var watchers = count[1];

										setText('#scopes', scopes);
										setText('#watchers', watchers);

										var log = 'NG-PERF: Digest Cycle #' + digestCycles + ': ' + digestMs.toFixed(1) + ' ms, ' +
											'Scopes: ' + scopes + ', Watchers: ' + watchers +
											' [Overhead: ' + (performance.now() - digestEnd).toPrecision(3) + ' ms]';
										$log.debug(log);
										if ($window.console.timeStamp) {
											$window.console.timeStamp(log);
										}

										// Register an async function to run first.
										//
										// NOTE: This technique for timing the $digest cycles
										//       DOES capture time spent processing the asyncQueue!
										// $rootScope.$$asyncQueue.unshift({
										// 	scope: $rootScope,
										// 	expression: function (scope) {
										// 		// $log.debug('$rootScope.$evalAsync: digestStart');
										// 		digestStart = performance.now();
										// 		digestCycles++;
										// 	}
										// });

										// Clear digestStart for next "dirty loop."
										digestStart = 0;
									}
								});

								return null;
							}
						});
					}
				}
			};
		}
	]);

})(angular);

;
(function (angular) {

	'use strict';

	/**
	* @ngdoc directive
	* @name directive:haBookingForm
	* @description
	* # haBookingFrom
	*/

	var module = angular.module('haBookingFormModule', ['haFeatureFlagsModule', 'duScroll']);

	//	theme - sets theme a, b, c.  Default is a.
	//	promos - sets promos on/off. default is true/on.
	//  promoCodeValue - sets code inside of promo input.
	//	travelCredit - sets travelCredit on/off. Default is true/on.
	//	btnSearchFlights - hides/shows search flights button.  Default is true/on.
	//	btnSearchFlightsAndHotels - hides/shows search flights and hotels button. Default is true/on.
	//	recentSearchesSwitch - hides/shows recent searches.  Default is on.
	//	advancedSearchLink - hides/shows advanced search link.  Default is on for all themes except a (which is advanced search).
	//	flexibilePriceViewLink - hides/shows Monthly View Of Fares link.  Default is on.
	//	flightScheduleLink - hides/shows Flight Schedule link.  Default is on
	//  roundTripFlight - sets whether flight is round trip or one-way. Default is true (round trip).
	//	oneWay - allows one-way trips.  Default is on.
	//	roundTrip - allows round-trip trips.  Default is on.
	//	multiCity - allows multi-city trips.  Default is on.
	//	adults - hides/shows adults dropdown.  Default is on.  When hidden, adultcount will be 1.
	// 	adultCount - number of adults. Default is 1.
	//	children - hides/shows children dropdown.  Default is on.  When hidden, childcount will be 0.
	// 	childrenCount - number of children. Default is 0.
	//	searchPosition - Moves search buttons right and left.  Default is right (advanced booking form).
	//	expanded - Sets expanded on/off.  Default is on. Not expanded is used for home page and sticky booking widget.
	//  defaultDestinationImage - default background image for interstitial. Set to enable the image preloading. Default is off.
	//  milesRadioButton - hides/shows Miles radio button. Default is off.
	//  milesRadioOverride - Overrides the Miles radio selection instead after reading from cookie.  Default is off.
	//  childCountWarning - hides/shows the child traveling alone warning tooltip.  Default is on.
	//  departureLocation - set the from location in booking. Default is undefined.
	//  arrivalLoaction - set the to location for the flight. Ex: HNL
	//  departureDate - date that fills in the depart input. Ex: 20171026T000000
	//  arrivalDate - date that fills in the return input.

	module.directive('haBookingForm', [
		'$document',
		'haHttpService',
		'haConfig',
		'haGlobals',
		'haFeatureFlags',
		'$window',
		'haUtils',
		'haCitiesSvc',
		'haDateUtils',
		'haUnavailableDays',
		'$timeout',
		'haModal',
		'haEcertAPI',
		'$filter',
		'$q',
		'$compile',
		'$log',
		'haSitecoreStrings',
		function ($document,
			haHttpService,
			haConfig,
			haGlobals,
			haFeatureFlags,
			$window,
			haUtils,
			haCitiesSvc,
			haDateUtils,
			haUnavailableDays,
			$timeout,
			haModal,
			haEcertAPI,
			$filter,
			$q,
			$compile,
			$log,
			$scs) {
			return {
				//templateUrl: haConfig.getTemplateUrl('ha-booking-form.html'),	// Moved to razor - /areas/book/views/shared/ha-booking-form.cshtml
				restrict: 'A',
				link: function (scope, el, attrs) {

					var $bookingform = $('form[name="flightSearch"]');

					haCitiesSvc.preloadCities();

					//RT and OW datepickers
					scope.range_datepicker_config = {
						start: "[name='_FlightSearchSegmentList[0].DepartureDate']",
						end: "[name='_FlightSearchSegmentList[1].DepartureDate']"
					};

					//MultiCity datepickers
					scope.multicity_datepicker_configs = [];
					scope.multicity_datepicker_config = function (idx) {
						if (scope.multicity_datepicker_configs[idx]) {
							return scope.multicity_datepicker_configs[idx];
						}

						var cfg = scope.multicity_datepicker_configs[idx] = {
							viewing: getDepart(idx - 1) || moment().startOf('day'),
							range_start: getDepart(idx - 1) || moment().startOf('day'),
							range_end: getDepart(idx + 1) || moment().add(331, 'days').startOf('day'),
							idx: idx
						};
						cascadeMulticityCalendarSettings();
						return cfg;
					};

					function getDepart(idx) {
						var depart = scope.departDate[idx];
						return depart && moment(depart).startOf('day');
					}

					function cascadeMulticityCalendarSettings() {
						//(re)connect the start_range and end_range of each calendar with it's neighbor
						scope.multicity_datepicker_configs.forEach(function (config, idx) {
							var prev_selected = (function () {
								for (var i = idx; i;) {
									if (scope.departDate[--i]) {
										return moment(scope.departDate[i]);
									}
								}
							})();
							if (!getDepart(idx) && prev_selected) {
								transpose(config.viewing, prev_selected);
							}
							transpose(config.range_start, getDepart(idx - 1) || prev_selected || moment());
							transpose(config.range_end, getDepart(idx + 1) || moment().add(331, 'days'));
						});
					}

					function transpose(target, dt) {
						target.year(dt.year()).month(dt.month()).date(dt.date());
					}

					for (var i = 0; i < 6; i++) {
						scope.$watch('departDate[' + i + ']', function () {
							cascadeMulticityCalendarSettings();
						});
					}

					// EVENT HANDLERS
					//*******************
					// Date Input focused
					scope.$on('dateInputFocused', function (e, currentDateChoice, idx) {
						scope.currentDateChoice = currentDateChoice;
						scope.idx = idx;
						angular.forEach(scope.calendarOpen, function (val, key) {
							scope.calendarOpen[key] = false;
						});
						scope.calendarOpen[idx] = true;

						// Timeout to skip the first click event
						wireCalendarCloseEvent();
					});

					// Calendar clicked
					scope.$on('setDate', function (e, date) {

						if (!scope[scope.currentDateChoice]) {
							return;
						}

						// Did they click the already set depart or return
						scope[scope.currentDateChoice][scope.idx] = date || '';
						// reset the other date if needed
						if (scope.currentDateChoice === 'departDate' && scope.returnDate[scope.idx] && date > scope.returnDate[scope.idx]) { //depart
							scope.returnDate[scope.idx] = undefined;
						}
						else if (scope.currentDateChoice === 'returnDate' && scope.departDate[scope.idx] && date < scope.departDate[scope.idx]) {  //return
							scope.departDate[scope.idx] = undefined;
						}
						// reset all prior or next dates before/after date chosen for multi-city
						if (scope.tripType === 0) {
							var c;
							for (c = scope.idx; c > 0; c--) {
								if (scope.departDate[c - 1] && date < scope.departDate[c - 1]) {
									scope.departDate[c - 1] = date;
								}
							}
							for (c = scope.idx; c < scope.legs.length - 1; c++) {
								if (scope.departDate[c + 1] && date > scope.departDate[c + 1]) {
									scope.departDate[c + 1] = date;
								}
							}
						}

						scope.$digest();
					});

					// Airport input focused
					scope.$on('airportInputFocused', function (e, el) {
						var $li = el.closest('li');
						var idx = $li.index();
						scope.idx = idx;

						if (!scope.expanded) {
							scope.setExpanded();
						}
					});

					scope.$on('haWhereWeFlyPinClicked', function () {
						if (!scope.expanded) {
							$timeout(function () {
								scope.setExpanded();
							}, 500);
						}
					});

					// Airport value changed
					scope.$on('airportChanged', function airportChanged() {
						angular.forEach(scope.legs, function (leg) {
							leg.invalidPair = false;
						});
						var legs = scope.legs;
						var isRoundtrip = scope.tripType === 2;
						// The round trip booking form doesn't have <inputs> that bind
						//  to the 2nd leg of the trip- so we need to sync them manually.
						if (legs[0] && isRoundtrip) {
							if (legs.length < 2) {
								legs[1] = {};
							}
							legs[1].origin = legs[0].destination;
							legs[1].destination = legs[0].origin;
						}

						var leg = legs && legs[scope.idx];
						if (leg && (leg.origin && leg.origin.Code) && (leg.destination && leg.destination.Code)) {

							haUnavailableDays.getUnavailableDays(leg, scope)
								.then(function (ud) {
									//ud is an array [depart unavailables, return unavailables]
									//make sure any selected dates are still valid
									if (scope.departDate[scope.idx] && ud[0].CalendarYears.contains(scope.departDate[scope.idx])) {
										delete scope.departDate[scope.idx];
									}
									if (scope.returnDate[scope.idx] && ud[1].CalendarYears.contains(scope.returnDate[scope.idx])) {
										delete scope.returnDate[scope.idx];
									}

									scope.unavailableDays[scope.idx] = angular.isArray(ud) ? ud : [];
									scope.intl = ((leg.origin && leg.origin.Market === 3) || (leg.destination && leg.destination.Market === 3)) ? true : false;
									//recheck passenger count as INTL may have changed
									checkPassengerCount();
								});

							getMessages(scope.idx);
							//reset duplicate flag when an airport changes
							checkDuplicateLegs();
							//non contiguous legs are not supported
							//checkContiguousLegs();

							// preload destination image if the change was on the first segment and preloading is turned on
							if (scope.idx === 0 && scope.defaultDestinationImage !== '') {
								// preload destination image
								var imgURL = leg.destination.ImageURL;
								if (imgURL === '') {
									imgURL = scope.defaultDestinationImage;
								}
								var pic = new Image();
								pic.src = imgURL;
							}
						}

						updateRefundableFareOption(); // update the display of refundable fare checkbox
						updateMilesOption(); // enable/disable miles option
						scope.$broadcast('airport changed'); // for datepicker
					});

					// Search recalled from sticky
					scope.$on('recallSearch', function (e, idx) {
						scope.recallSearch(idx);
					});

					// WATCHERS
					//*******************
					// Watch Passenger Counts
					scope.$watchCollection('[pax.adultCount, pax.childCount]', function () {
						checkPassengerCount();
					});

					// Watch depart date
					scope.$watchCollection('departDate', function () {
						if (scope.departDate[scope.idx] && !scope.returnDate[scope.idx] && scope.tripType === 2) {
							scope.currentDateChoice = 'returnDate';
						} else if (scope.departDate[scope.idx] && scope.returnDate[scope.idx]) {
							delayCalendarClose(scope.idx);
						} else if (scope.tripType !== 2) {
							delayCalendarClose(scope.idx);
						}

						validateLowFareDuration(); // validate low fare duration
					});

					// Watch return date
					scope.$watchCollection('returnDate', function () {
						if (scope.returnDate[scope.idx] && !scope.departDate[scope.idx]) {
							scope.currentDateChoice = 'departDate';
						} else if (scope.returnDate[scope.idx] && scope.departDate[scope.idx]) {
							delayCalendarClose(scope.idx);
						}

						validateLowFareDuration(); // validate low fare duration
					});

					// Watch legs
					scope.$watchCollection('legs', function () {
						if (scope.milesRadioButton) {
							updatePaymentType();
						}

						validateLowFareDuration(); // validate low fare duration
					});

					// Watch recent open/close
					scope.recentSearches = { open: false };
					scope.$watch('recentSearches.open', function (isOpen) {
						if (typeof isOpen === 'undefined') {
							return;
						}

						if (isOpen) {
							$timeout(function () {
								$('body').on('click.recent', function () {
									scope.$apply(function () {
										scope.recentSearches.open = false;
									});
								});
							}, 10);

						} else {
							$('body').off('click.recent');
						}
					});

					// update refundable fare checkbox on miles option change
					scope.$watch('paymentType.type', function () {
						updateRefundableFareOption();
						flexibleDatesCalendar();
					});

					// update refundable fare checkbox on promo selection change
					scope.$root.$watch('selectedPromoId', function () {
						updateRefundableFareOption();
						updateMilesOption();
						updatePromoCodeOption();
					});

					// Change or re-enter promo code will reset the apply flag
					scope.$watch('promoCode.Code', function () {
						if (scope.flightSearch && scope.flightSearch.PromoCode) {
							scope.flightSearch.PromoCode.$setValidity('promoCodeNotFound', true);
						}
					});

					// LOCAL FUNCTIONS
					//*******************
					// Wire calendar close event
					function wireCalendarCloseEvent() {
						$document.off('click.closeCalendar');
						$document.on('click.closeCalendar', function (e) {
							var $targ = $(e.target);

							if ($targ.closest('.originDestinationWrap' + scope.idx).length) {
								return;
							}

							$document.off('click.closeCalendar');
							scope.currentDateChoice = '';
							angular.forEach(scope.calendarOpen, function (val, key) {
								delayCalendarClose(key);
							});
							scope.$digest();
						});
					}

					// delay cal close so user can see selection
					function delayCalendarClose(i) {
						$timeout(function () {
							scope.calendarOpen[i] = false;
						}, 500);
					}

					// Check and validate passenger counts/types
					function checkPassengerCount() {
						scope.isChildCountInvalid = (parseInt(scope.pax.adultCount, 10) === 0 && parseInt(scope.pax.childCount, 10) > 0);
						scope.isSearchDisabled = (scope.pax.adultCount < 1 && scope.pax.childCount < 1);
						var paxCount = parseInt(scope.pax.adultCount, 10) + parseInt(scope.pax.childCount, 10);
						scope.isCountValid = paxCount > 0 && paxCount < 8;
						if (scope.flightSearch) {
							scope.flightSearch.$setValidity('haPassengerCount', scope.isCountValid);
						}
					}

					// Update the selection of payment type radio button
					function updatePaymentType() {
						// if Promo is applied or Multi-City, set to dollars
						if (scope.$root.selectedPromoId && scope.tripType > 0) {
							scope.paymentType.type = '0';
							angular.forEach(scope.legs, function (leg) {
								leg.isMiles = false;
							});
						}
						switch (scope.tripType) {
							case 0:
								scope.paymentType.type = '0';
								break;
							case 1:
								if (scope.legs.length === 1 && scope.legs[0].isMiles) {
									scope.paymentType.type = '1';
								} else {
									scope.paymentType.type = '0';
								}
								break;
							case 2:
								if (scope.legs.length === 2 && scope.legs[0].isMiles && scope.legs[1].isMiles) {
									scope.paymentType.type = '1';
								} else if (scope.legs.length === 2 && !scope.legs[0].isMiles && scope.legs[1].isMiles) {
									scope.paymentType.type = '2';
								} else if (scope.legs.length === 2 && scope.legs[0].isMiles && !scope.legs[1].isMiles) {
									scope.paymentType.type = '3';
								} else {
									scope.paymentType.type = '0';
								}
								break;
						}
						if (scope.milesRadioOverride !== '') {
							scope.paymentType.type = scope.milesRadioOverride;
						}
					}

					// check if all airports are either HA or Ohana
					function isHARoute() {
						var invalidOrigin = false;
						var invalidDestination = false;
						// check the validity of origin if origin is set
						if (scope.legs[0] && scope.legs[0].origin) {
							// origin cannot be code share, unless it's Ohana flights (MKK, JHM, or LNY)
							if (scope.legs[0].origin.IsCodeShare && scope.legs[0].origin.Code !== 'MKK' && scope.legs[0].origin.Code !== 'JHM' && scope.legs[0].origin.Code !== 'LNY') {
								invalidOrigin = true;
							}
						}
						// check the validity of destination if destination is set
						if (scope.legs[0] && scope.legs[0].destination) {
							// destination cannot be code share, unless it's Ohana flights (MKK, JHM, or LNY)
							if (scope.legs[0].destination.IsCodeShare && scope.legs[0].destination.Code !== 'MKK' && scope.legs[0].destination.Code !== 'JHM' && scope.legs[0].destination.Code !== 'LNY') {
								invalidDestination = true;
							}
						}

						return (!invalidOrigin && !invalidDestination);
					}

					// Update the visibility of Refundable fare checkbox
					function updateRefundableFareOption() {
						if (scope.$switch('BookingWidget:enablerefundablefares') && scope.refundableFareOption && !scope.enableTCR) {
							// only show Refundable fare option if it's not multicity, is not miles booking
							if (!scope.$root.selectedPromoId && scope.tripType !== 0 && (scope.paymentType.type === undefined || scope.paymentType.type === '0') && isHARoute()) {
								scope.showRefundableFare = true;
								return;
							}
						}
						scope.showRefundableFare = false;
					}
					function flexibleDatesCalendar() {
						if (scope.paymentType.type === '1') {
							scope.flexibleDatesCalendarChecked = false;
							document.getElementById("flexibleDatesCalendar").checked = false;
							return;
						}
						scope.flexibleDatesCalendar = null;
					}

					// Enable/Disable Miles option
					function updateMilesOption() {
						// disable Miles option if promo is applied, it's not HA Route, or Multicity
						scope.disableMilesOption = (!(!scope.$root.selectedPromoId) || !isHARoute() || scope.tripType === 0);

						// if disabled or it is one way and miles/dollars is selected, default to Dollars
						if (scope.disableMilesOption || (scope.tripType === 1 && parseInt(scope.paymentType.type, 10) > 1)) {
							scope.paymentType.type = '0';
						}
					}

					// Enable/Disable Promo Code option
					function updatePromoCodeOption() {
						// only if promoCodeOption is true, no ecert or ETCO is applied
						scope.promoCodeOptionEnabled = (scope.promoCodeOption && !scope.$root.selectedPromoId && !scope.enableTCR);
					}

					// Check if the duration is valid for Low Fare chart
					function validateLowFareDuration() {
						// if flexiblePriceViewLink is enabled and is Round Trip, check if duration is valid
						if (scope.flexiblePriceViewLink) {
							if (scope.departDate[0] && scope.returnDate[0] && scope.tripType === 2) {
								scope.isValidLowFareDuration = (haDateUtils.numDaysDifference(scope.departDate[0], scope.returnDate[0]) <= 60);
							} else {
								scope.isValidLowFareDuration = true;
							}
						}
					}

					// Check legs for duplicates in multi-city
					function checkDuplicateLegs() {

						//first set/reset the dupe check fo valid
						var legDuplicateCheckValid = true;
						scope.flightSearch.$setValidity('haDuplicateLegs', legDuplicateCheckValid);

						$timeout(function () {
							if (scope.tripType !== 0 || scope.legs.length < 2 || !scope.flightSearch.$valid) {
								return;
							}

							//need to check every leg against every other leg
							var currOrigin;
							var currDestination;
							for (var legIndexOuter = 0; legIndexOuter < scope.legs.length; legIndexOuter++) {
								currOrigin = scope.legs[legIndexOuter].origin.Code;
								currDestination = scope.legs[legIndexOuter].destination.Code;

								for (var legIndexInner = 0; legIndexInner < scope.legs.length; legIndexInner++) {
									if (legIndexOuter !== legIndexInner) {

										legDuplicateCheckValid = scope.tripType !== 0 || currOrigin !== scope.legs[legIndexInner].origin.Code || currDestination !== scope.legs[legIndexInner].destination.Code;

										scope.flightSearch.$setValidity('haDuplicateLegs', legDuplicateCheckValid);

										if (!legDuplicateCheckValid) {
											return;
										}
									}
								}
							}
						});
					}

					//function checkContiguousLegs() {
					//	//first set/reset the dupe check fo valid
					//	var legContiguousCheckValid = true;
					//	scope.flightSearch.$setValidity('haContiguousLegs', legContiguousCheckValid);
					//	if (scope.tripType !== 0 || scope.legs.length < 2 || !scope.flightSearch.$valid) {
					//		return;
					//	}

					//	for (var leg = 0; leg + 1 < scope.legs.length; leg++) {
					//		var currentLegDestination = scope.legs[leg].destination.Code;
					//		var nextLegOrigin = scope.legs[leg + 1].origin.Code;
					//		if (currentLegDestination != nextLegOrigin) {
					//			scope.legs[leg + 1].invalidPair = true;
					//			scope.flightSearch.$setValidity('haContiguousLegs', false);
					//		}
					//	}
					//}

					// Get calendar leg messages
					function getMessages(idx) {
						if (typeof idx === 'undefined') {
							return;
						}
						scope.messages[idx] = [getMessage(idx, 'origin'), getMessage(idx, 'destination')].filter(function (x) {
							return !!x;
						}).map(function (m) {
							return '<div class="cal-message">' +
								'<i class="ha-icon fontIcon24-' + (scope.widgetDatepickerAlertIcon || 'info-circle') + '"></i>' +
								'<p>' + m + '</p>' +
								'</div>';
						}).join('');
					}

					function getMessage(idx, endpoint) {
						return (endpoint = scope.legs[idx][endpoint]) && endpoint.ShowCalendarMessage ? endpoint.CalendarMessage : '';
					}

					var today = new Date(new Date().setHours(0, 0, 0, 0));

					function apply(query, ignoreDates) {
						query = query || {};
						// if date is invalid or if it is multi-city search when multi-city is disabled, don't load the cookie
						var searchIsValid = query.legs && (ignoreDates || query.legs[0].departDate >= today) && !(query.tripType === 0 && scope.multiCity === false);
						if (searchIsValid) {
							scope.legs = [];
							scope.departDate = [];
							scope.returnDate = [];
							scope.unavailableDays = [];
							scope.messages = [];

							query.legs.forEach(function (l, i) {
								haCitiesSvc.getCityMap().then(function (map) {
									var leg = scope.legs[i] = {
										origin: map[l.origin.code],
										destination: map[l.destination.code],
										isMiles: !!l.miles
									};

									scope.intl = leg.origin.Market === 3 || leg.destination.Market === 3;

									haUnavailableDays.getUnavailableDays(leg, scope).then(function (ud) {
										scope.unavailableDays[i] = angular.isArray(ud) ? ud : [];
										checkPassengerCount();
										getMessages(i);
									});
								});

								scope.departDate[i] = l.departDate;

								var segReturn = query.legs[i + 1];
								if (segReturn) {
									scope.returnDate[i] = segReturn.departDate;
								}
							});
						}

						// Travel Credits and NITP bookings can only be redeemed for one adult.
						scope.pax.adultCount = scope.enableTCR || scope.disableAdultFieldForNITP ? 1 : num(query.adults, 1);
						scope.pax.childCount = scope.enableTCR || scope.disableAdultFieldForNITP ? 0 : num(query.children, 0);
						scope.tripType = num(query.tripType, 2);

						if (scope.flightSearch) {
							scope.flightSearch.IsRefundableCheck = !!query.refundable;
						}

						checkPassengerCount();
						updatePaymentType();
					}

					function applyWithAttributes(query) {
						query = query || {};
						scope.legs = [];
						scope.departDate = [];
						scope.returnDate = [];
						scope.unavailableDays = [];
						scope.messages = [];

						query.legs.forEach(function (l, i) {
							haCitiesSvc.getCityMap().then(function (map) {
								var leg = scope.legs[i] = {
									origin: map[l.origin.code],
									destination: map[l.destination.code],
									isMiles: !!l.miles
								};

								if (leg.origin && leg.origin.Market && leg.destination && leg.destination.Market) {
									scope.intl = leg.origin.Market === 3 || leg.destination.Market === 3;
								} else if (leg.origin && !leg.destination) {
									scope.intl = leg.origin.Market === 3;
								} else {
									scope.intl = leg.destination ? leg.destination.Market === 3 : '';
								}

								// Make sure that the leg has a departure date and arrival date
								if (leg.origin && leg.destination) {
									haUnavailableDays.getUnavailableDays(leg, scope).then(function (ud) {
										scope.unavailableDays[i] = angular.isArray(ud) ? ud : [];
										checkPassengerCount();
										getMessages(i);
									});
								}
							});

							scope.departDate[i] = l.departDate;

							var segReturn = query.legs[i + 1];
							if (segReturn) {
								scope.returnDate[i] = segReturn.departDate;
							}
						});

						// Travel Credits and NITP bookings can only be redeemed for one adult.
						scope.pax.adultCount = scope.enableTCR || scope.disableAdultFieldForNITP ? 1 : num(query.adults, 1);
						scope.pax.childCount = scope.enableTCR || scope.disableAdultFieldForNITP ? 0 : num(query.children, 0);
						scope.tripType = num(query.tripType, 2);

						if (scope.flightSearch) {
							scope.flightSearch.IsRefundableCheck = !!query.refundable;
						}

						checkPassengerCount();
						updatePaymentType();
					}

					function num(value, deflt) {
						value = Number(value);
						return isNaN(value) ? deflt : value;
					}

					function processRecentSearches() {
						var recentSearchArr = haUtils.getFlightQueryModelRecentCookie();
						scope.flightQueryCookieArr = recentSearchArr && recentSearchArr.filter(function (search) {
							var depart = search.FlightSearchSegmentList[0].DepartureDate.substr(0, 10);//make sure it's only the date
							depart = moment(depart, "YYYY-MM-DD").toDate();
							return depart >= today;
						});
					}

					// Set booking form attributes
					function setAttributes() {
						//	theme - sets theme a, b, c.  Default is a.
						//	promos - sets promos on/off. default is true/on.
						//  promoCodeValue - presets the promo code input.
						//	travelCredit - sets travelCredit on/off. Default is true/on.
						//	btnSearchFlights - hides/shows search flights button.  Default is true/on.
						//	btnSearchFlightsAndHotels - hides/shows search flights and hotels button. Default is true/on.
						//	recentSearchesSwitch - hides/shows recent searches.  Default is on.
						//	advancedSearchLink - hides/shows advanced search link.  Default is on for all themes except a (which is advanced search).
						//	flexibilePriceViewLink - hides/shows Monthly View Of Fares link.  Default is on.
						//	flightScheduleLink - hides/shows Flight Schedule link.  Default is on
						//  roundTripFlight - sets whether flight is round trip or one-way. Default is true (round trip).
						//	oneWay - allows one-way trips.  Default is on.
						//	roundTrip - allows round-trip trips.  Default is on.
						//	multiCity - allows multi-city trips.  Default is on.
						//	adults - hides/shows adults dropdown.  Default is on.  When hidden, adultcount will be 1.
						// 	adultsCount - number of adults. Default is 1.
						//	children - hides/shows children dropdown.  Default is on.  When hidden, childcount will be 0.
						//  childrenCount - number of children. Default is 0.
						//	searchPosition - Moves search buttons right and left.  Default is right (advanced booking form).
						//	expanded - Sets expanded on/off.  Default is on. Not expanded is used for home page and sticky booking widget.
						//  defaultDestinationImage - default background image for interstitial. Set to enable the image preloading. Default is off.
						//  milesRadioButton - hides/shows Miles radio button. Default is off.
						//  milesRadioOverride - Overrides the Miles radio selection instead after reading from cookie.  Default is off.
						//  childCountWarning - hides/shows the child traveling alone warning tooltip.  Default is on.
						//  refundableFareOption - hides/shows the Refundable Fare Only option.  Default is on.
						//  promoCodeOption - hides/shows the promo code inputs.  Default is off.
						//  departureLocation - set the from location in booking. Default is undefined. EX: HNL
						//  arrivalLoaction - set the to location for the flight. EX: KOA
						//  departureDate - date that fills in the depart input. Ex: 20171026T000000
						//  arrivalDate - date that fills in the return input.
						$timeout(function () {

							// Default properties to true
							"promos,travelCredit,btnSearchFlights,btnSearchFlightsPlusHotels,recentSearchesSwitch,advancedSearchLink,flexiblePriceViewLink,flightScheduleLink,oneWay,roundTrip,multiCity,adults,children,expanded,childCountWarning,refundableFareOption,Enable1AOnLegacy".split(',')
								.forEach(function (prop) {
									scope[prop] = !(attrs[prop] && attrs[prop] === 'false');
								});

							// Default properties to false
							['milesRadioButton', 'promoCodeOption']
								.forEach(function (prop) {
									scope[prop] = !!(attrs[prop] && attrs[prop] === 'true');
								});

							if (scope.isAffiliate || scope.isCorporate) {
								scope.btnSearchFlightsPlusHotels = false;
							}
							// String property defaults
							scope.theme = attrs.theme || 'a';
							scope.milesRadioOverride = attrs.milesRadioOverride || '';
							scope.searchPosition = attrs.searchPosition || 'right';
							scope.defaultDestinationImage = attrs.defaultDestinationImage || '';

							validateLowFareDuration(); // validate low fare duration
							updatePromoCodeOption();
						});
					}

					// SCOPE METHODS
					//*******************
					// Set trip type from click
					scope.setTripType = function (t) {
						// reset submission state so ha-errors doesn't get confused
						// we need this since form elements are getting swapped in and out, but parent form persists
						scope.flightSearch.$submitted = false;
						$bookingform.removeClass('submitted');

						// incase switch to multicity after roundtrip search remove the return leg
						if (scope.tripType != 0 && t === 0) {
							scope.deleteLeg(1);
						}

						scope.tripType = t;
						// for one way, make sure only one leg. Causes error on submit
						if (t === 1) {
							scope.legs = scope.legs.slice(0, 1);
						}
						scope.showChartError = false;
						//for multi-city, remove any eCert Promos if we are on the advanced search page
						if (scope.$root.selectedPromoId && scope.tripType === 0) {
							scope.$root.discountRemoved = true;
							scope.$root.selectedPromoId = null;
						} else { //if going back to round trip or one way, re-show the Promo if the model value exists
							if (scope.PromoModel && scope.PromoModel.Promo && scope.tripType !== 0 && !scope.$root.discountPermanentlyRemoved) {
								scope.$root.selectedPromoId = scope.PromoModel.Promo.OfferId;
								scope.$root.discountRemoved = false;
							}
						}
						// enable/disable update miles option
						updateMilesOption();
						scope.$broadcast('trip type changed', t);
					};

					scope.focusTripTypeSelection = function () {
						scope.expanded = true;
					};

					scope.focusBackOnTripType = function () {
						scope.recentSearches.open = false;
					};

					scope.$on('airportInputFocused', function () {
						scope.recentSearches.open = false;
					});

					scope.$on('haAlertClosed',
						function ($event, alertId) {
							if (alertId === 'promoCodeError') {
								scope.promoCodeNotFound = false;
							}

						});

					function getLeg(i, depart) {
						//Sometimes, it is a roudtrip but scope.legs DOES NOT
						//have a second leg. This makes an ugly mess where
						//we need to reverse the direction of the first leg.
						var origin = (scope.legs[i] && scope.legs[i].origin) || scope.legs[0].destination;
						var destination = (scope.legs[i] && scope.legs[i].destination) || scope.legs[0].origin;
						return {
							origin: {
								code: origin.Code,
								display: origin.DisplayName
							},
							destination: {
								code: destination.Code,
								display: destination.DisplayName
							},
							departDate: depart
						};
					}

					function getLegs(redirection) {
						var legs = [getLeg(0, scope.departDate[0])];
						if (scope.tripType === 2) {
							legs.push(getLeg(1, scope.returnDate[0]));
						}
						else if (scope.tripType === 0 && redirection) {
							for (var i = 1; i < scope.legs.length; i++) {
								legs.push(getLeg(i, scope.departDate[i]));
							}
						}
						return legs;
					}

					// Open price chart modal
					scope.openPriceChart = function (display) {
						display = display || 'calendar';
						if (scope.flightSearch.$valid) {
							haModal(haConfig.getTemplateUrl('/Book/FlightSearch/ha-flexible-price-modal.html'), {
								id: 'FlexiblePriceView',
								backdrop: 'true',
								extendScope: {
									legs: getLegs(false),
									adults: scope.pax.adultCount,
									children: scope.pax.childCount,
									display: display
								}
							});
						} else {
							scope.showChartError = true;
						}
					};

					// Add leg
					scope.addLeg = function (count) {
						$bookingform.removeClass('submitted');
						scope.flightSearch.$submitted = false;
						for (var i = 0; i < count; i++) {
							scope.leg = {};
							scope.legs.push(scope.leg);
						}
					};

					// Delete leg
					scope.deleteLeg = function (idx) {
						scope.legs.splice(idx, 1);
						scope.departDate.splice(idx, 1);
						scope.unavailableDays.splice(idx, 1);
						scope.messages.splice(idx, 1);
						scope.multicity_datepicker_configs.splice(idx, 1);
						cascadeMulticityCalendarSettings();
						checkDuplicateLegs();
						//checkContiguousLegs();
					};

					// Get calendar heading
					scope.getCalendarHeading = function (legType) {
						if ($el.context.textContent && $el.context.textContent.length > 0) {
							var departdateval = $el.context.textContent.split('\n')[4].replace(',', '');
							var returndateval = $el.context.textContent.split('\n')[13].replace(',', '');
							return (legType !== 'returnDate') ? departdateval : returndateval;
						} else {
							return (legType !== 'returnDate') ? $scs('BookingWidget.departdate') : $scs('BookingWidget.returndate');	// TODO: PULL FROM XLations
						}
					};

					// Scroll Form on home page when expanded
					scope.scrollForm = function (value) {
						// Auto-scroll near top of booking widget
						var homepageWidget = angular.element('.homepage-widget');
						if (homepageWidget.length > 0) {
							$document.scrollToElement(homepageWidget, 20, 450, function easeInOutQuart(t) {
								return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t;
							}).then(function () {
								scope.expanded = value;
							});
						} else {
							scope.expanded = value;
						}
					};

					// Scroll to a specified id
					scope.scrollTo = function (id) {
						// Auto-scroll to element after timeout
						$timeout(function () {
							var element = angular.element('#' + id);
							if (element.length > 0) {
								$document.scrollToElement(element, 20, 450, function easeInOutQuart(t) {
									return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t;
								});
							}
						}, 0);
					};

					// Check unavailable
					scope.getIsUnavailable = function (date) {

						// Before today
						if (haDateUtils.isBefore(date, today) && !haDateUtils.isSameDay(date, today) || haDateUtils.isAfter331(date, today)) {
							return true;
						}


						// Dates chosen but still not available, picking depart (must be before return), picking return (must be after depart)
						var currentDateChoice = scope.currentDateChoice;
						var isUnavail = haUnavailableDays.isUnavailable(date, scope.unavailableDays[scope.idx], currentDateChoice);
						var departDate = scope.departDate[scope.idx];
						var returnDate = scope.returnDate[scope.idx];

						if (departDate && returnDate && scope.tripType > 0) {
							//if we have both dates, just return unavailable days
							return isUnavail;
						}

						// Unavailable, before depart or after return
						return (isUnavail) ||
							((scope.tripType === 2) &&
								((currentDateChoice === 'returnDate' && departDate && haDateUtils.isBefore(date, departDate)) ||
									(currentDateChoice === 'departDate' && returnDate && haDateUtils.isAfter(date, returnDate))));
					};

					scope.infantInfoModal = function () {
						if (!scope.isCorporate || scope.isAffiliate) {
							haModal(haConfig.getTemplateUrl('ha-child-infant-info-modal.html'),
								{
									id: 'infantModal',
									backdrop: 'true'
								});
						}
						console.log(scope.isAffiliate);
					};

					scope.infantInfoContentCheck = function () {
						var infantIconHide;
						if ($scs('BookingWidget.childinformationmodal') == null || $scs('BookingWidget.childinformationmodal') === "" || $scs('BookingWidget.childinformationmodal') === "[BookingWidget.childinformationmodal]") {
							return infantIconHide = true;
						} else {
							return infantIconHide = false;
						}
					}

					// Handle flights & hotel submit action
					scope.searchFlightsPlusHotels = function (e) {
						e.preventDefault();


						// Promo code is optional even if invalid or has not been applied
						if (scope.flightSearch.PromoCode !== undefined && !scope.flightSearch.PromoCode.$valid) {
							clearPromoCodeInput();
						}

						// Ensure that the form is valid OR that the only error is the passenger count, since we want
						// to defer to the third party site's error.
						if (scope.flightSearch.$valid || (_.size(scope.flightSearch.$error) === 1 && scope.flightSearch.$error.haPassengerCount)) {

							//Set Defaults
							var childAge = 6, numRoom = 1, packageType = 'FlightHotel', cabinClass = 'e', fromTime = 362, toTime = 362;

							var postObject = {};
							var fromDate = scope.departDate[0].YYYY_MM_DD();
							var toDate = scope.returnDate[0].YYYY_MM_DD();
							var fromAirport = scope.legs[0].origin.Code;
							var destination = scope.legs[0].destination.Code;
							var adults = scope.pax.adultCount;
							var children = scope.pax.childCount;

							postObject.FromAirport = fromAirport;
							postObject.Destination = destination;
							postObject.ToTime = toTime;
							postObject.FromTime = fromTime;
							postObject.NumRoom = numRoom;
							postObject.cabinClass = cabinClass;

							//Dynamically adding key value pairs for passengers
							for (var r = 0; r < numRoom; r++) {
								var adultKey = 'NumAdult-Room' + (r + 1);
								var childCountKey = 'NumChild-room' + (r + 1);
								postObject[adultKey] = adults;
								if (children > 0) {
									postObject[childCountKey] = children;
									for (var c = 0; c < children; c++) {
										var childKey = 'Room' + (r + 1) + '-Child' + (c + 1) + 'Age';
										postObject[childKey] = childAge;
									}
								}
							}

							postObject['mdpcid'] = haUtils.webtrends.tokenV2("FLTWIDGET.PACKAGE");

							var queryString = haUtils.createQueryString(postObject);
							var url = $scs('BookingWidget.searchpackagesurl');
							var loc = url + packageType + '/' + fromDate + '/' + toDate + queryString;
							$log.debug(loc);
							location.href = loc;

						} else {
							scope.flightSearch.$submitted = true;
							$bookingform.addClass('submitted');
							scope.$broadcast('validateForm');
							return;
						}
					};

					// Validate promo code when customer presses enter key from the promo code text box
					scope.applyPromoCodeOnEnter = function ($event) {
						if ($event.which === 13) {
							$event.preventDefault();
							scope.applyPromoCode();
						}
					};

					// Handle Affiliate code when customer clicks on button to apply

					function HandleAffiliate(promoCode) {
						var isErrorResponse = false;
						var errorMessage = ''; var error = '';
						haEcertAPI.HandleAffiliates(scope.promoCode.Code)
							.then(function (response) {
								if (response.data.IsSuccess && response.data.RedirectURL != null) {
									response.data.RedirectURL = response.data.RedirectURL;
									$window.location = response.data.RedirectURL;
								}
								else {
									if (response.data.ServiceErrors != null) {
										scope.promoCodeNotFound = true;
										scope.flightSearch.PromoCode.$setValidity('promoCodeNotFound', false);
									}
									else {
										errorMessage = 'Error While Processing Request';
									}
									error(response);
								}
							}, error);
					}

					// Handle NoName PromoCode When customer clicks on apply button
					function HandleNoName(promoCode) {
						var url = "/Ecertificate?ecertId=" + promoCode;
						if (url != null) {
							$window.location = url
						}
						else {
							scope.promoCodeNotFound = true;
							scope.flightSearch.PromoCode.$setValidity('promoCodeNotFound', false);
						}
					}

					// Validate promo code when customer clicks on button to apply
					scope.applyPromoCode = function () {
						haEcertAPI.validateRedeemPromoCode(scope.promoCode.Code).success(function (results) {
							if (results && results.IsSuccess && results.PromoCodePromotion) {
								if (results && results.IsSuccess && results.PromoCodePromotion.OfferType === 10) {
									handlePromoCodeApply(results.PromoCodePromotion);
								}
								else if (results && results.IsSuccess && results.PromoCodePromotion.OfferType === 3) {
									HandleNoName(scope.promoCode.Code);
								}
								scope.flightSearch.PromoCode.$setValidity('promoCodeNotFound', true);
							}
							else if (results.PromoCodePromotion == null) {
								HandleAffiliate(scope.promoCode.Code);
								scope.flightSearch.PromoCode.$setValidity('promoCodeNotFound', true);
							}
							else {
								$log.error('applyPromoCode failed');
								scope.promoCodeNotFound = true;
								scope.flightSearch.PromoCode.$setValidity('promoCodeNotFound', false);
							}
						}).error(function (err) {
							$log.error('Validate Promo Code encountered error: ' + err);
							scope.promoCodeNotFound = true;
							scope.flightSearch.PromoCode.$setValidity('promoCodeNotFound', false);
						})
					};

					scope.removePromoCode = function () {
						scope.promoCode.hasApplied = false;
						handlePromoCodeRemove();
					};

					// Submit form
					scope.submit = function ($event) {

						$event.preventDefault();
						scope.formSubmitted = true;

						// Promo code is optional even if invalid or has not been applied
						if (!scope.Enable1AOnLegacy) {
							if (scope.flightSearch.PromoCode !== undefined && !scope.flightSearch.PromoCode.$valid) {
								clearPromoCodeInput();
							}
						}

						//check form validity
						if (!scope.flightSearch.$valid) {
							scope.$broadcast('validateForm');
							return;
						}

						//Adding exit link tracking
						var trackingDestOrig = [];
						if (scope.legs) {
							angular.forEach(scope.legs, function (lg) {
								trackingDestOrig.push(lg.origin.Code);
								trackingDestOrig.push(lg.destination.Code);
							});
						}


						//Multicity Validation
						if (scope.tripType === 0) {

							if (scope.legs.length === 1) {
								//execute 1 way
								document.flightSearch.FlightQueryTypeId.value = 1;
							} else if (scope.legs.length === 2) {
								//execute round trip or open jaw
								//check city pairs, if they match in reverse, set to round trip, otherwise set to 0 (multicity open jaw)
								document.flightSearch.FlightQueryTypeId.value = (scope.legs[0].origin.Code === scope.legs[1].destination.Code && scope.legs[0].destination.Code === scope.legs[1].origin.Code) ? 2 : scope.tripType;
							}
							//check for duplicate legs
							checkDuplicateLegs();
							//check for contiguous legs
							//checkContiguousLegs();
							//if (scope.flightSearch.$error.haContiguousLegs) {return;}
						}

						//Set miles flag based on selection
						if (scope.milesRadioButton && scope.paymentType && scope.paymentType.type !== undefined) {
							switch (scope.paymentType.type) {
								case '1': // Miles
									scope.legs[0].isMiles = true;
									if (scope.tripType === 2) {
										if (scope.legs[1] === undefined) {
											scope.legs[1] = {
												origin: scope.legs[0].destination,
												destination: scope.legs[0].origin
											};
										}
										scope.legs[1].isMiles = true;
									}
									break;
								case '2': // Dollar Miles
									scope.legs[0].isMiles = false;
									if (scope.legs[1] === undefined) {
										scope.legs[1] = {
											origin: scope.legs[0].destination,
											destination: scope.legs[0].origin
										};
									}
									scope.legs[1].isMiles = true;
									break;
								case '3': // Miles Dollars
									scope.legs[0].isMiles = true;
									if (scope.legs[1] === undefined) {
										scope.legs[1] = {
											origin: scope.legs[0].destination,
											destination: scope.legs[0].origin
										};
									}
									scope.legs[1].isMiles = false;
									break;
								default: // Dollars
									scope.legs[0].isMiles = false;
									if (scope.tripType === 2) {
										if (scope.legs[1] === undefined) {
											scope.legs[1] = {
												origin: scope.legs[0].destination,
												destination: scope.legs[0].origin
											};
										}
										scope.legs[1].isMiles = false;
									}
									break;
							}
						}

						//check form validity again after leg check
						$timeout(function () {
							if (!scope.flightSearch.$valid) {
								scope.$broadcast('validateForm');
								return;
							}

							// ETCO validation check
							if (scope.enableTCR) {
								var today = new Date();
								today.setHours(0, 0, 0, 0);

								scope.ETCOResponseModel.Errors = [];

								// Cannot be codeshare other than ohana.
								if ((scope.legs[0].origin.IsCodeShare && scope.legs[0].origin.Code !== 'MKK' && scope.legs[0].origin.Code !== 'JHM' && scope.legs[0].origin.Code !== 'LNY') ||
									(scope.legs[0].destination.IsCodeShare && scope.legs[0].destination.Code !== 'MKK' && scope.legs[0].destination.Code !== 'JHM' && scope.legs[0].destination.Code !== 'LNY')) {

									scope.ETCOResponseModel.Errors.push('ETCOErrorCodeShare');
								}

								// Today must be between BookBetween dates.
								if (haDateUtils.isBefore(today, new Date(scope.ETCOResponseModel.BookBetweenFrom)) ||
									haDateUtils.isAfter(today, new Date(scope.ETCOResponseModel.BookBetweenTo))) {

									scope.ETCOResponseModel.Errors.push('ETCOErrorNoLongerValid');
								}

								// Travel dates must be between TravelPeriod dates.
								if (haDateUtils.isBefore(scope.departDate[0], new Date(scope.ETCOResponseModel.TravelPeriodFrom)) ||
									(scope.tripType === 1 && haDateUtils.isAfter(scope.departDate[0], new Date(scope.ETCOResponseModel.TravelPeriodTo))) ||
									(scope.tripType === 2 && haDateUtils.isAfter(scope.returnDate[0], new Date(scope.ETCOResponseModel.TravelPeriodTo)))) {

									scope.ETCOResponseModel.Errors.push('ETCOErrorDateRange');
								}

								// show validation error modal if any error
								if (scope.ETCOResponseModel.Errors.length > 0) {

									// get error message from sitecore
									$scs.get('ReservationsTravelCreditRedemption').then(function (data) {
										angular.forEach(scope.ETCOResponseModel.Errors, function (error, index) {
											if (data.hasOwnProperty(error.toLowerCase())) {
												scope.ETCOResponseModel.Errors[index] = data[error.toLowerCase()];
											}
										});

										haModal(haConfig.getTemplateUrl('ha-booking-etco-modal.html'), {
											id: 'promoValidationModal',
											backdrop: 'true',
											scope: scope
										});
									});

									return;
								}
							}
							if (!scope.Enable1AOnLegacy) {
								// Ecert validation check
								if (scope.tripType !== 0 && scope.PromoModel && scope.PromoModel.Promo && !scope.$root.discountRemoved) {
									var promoEligibilityRequest = {
										PromoCode: scope.PromoModel.Promo.OfferId,
										Origin: scope.legs[0].origin.Code,
										Destination: scope.legs[0].destination.Code,
										DepartureDate: $filter('date')(scope.departDate[0], 'yyyy-MM-dd'),
										ReturnDate: scope.returnDate[0] ? $filter('date')(scope.returnDate[0], 'yyyy-MM-dd') : null,
										TripType: scope.tripType,
										AdultCount: (!!scope.flightSearch.AdultCount && !!scope.flightSearch.AdultCount.$modelValue) ? scope.flightSearch.AdultCount.$modelValue : 0,
										ChildCount: (!!scope.flightSearch.ChildCount && !!scope.flightSearch.ChildCount.$modelValue) ? scope.flightSearch.ChildCount.$modelValue : 0,
										InfantCount: (!!scope.flightSearch.InfantCount && !!scope.flightSearch.InfantCount.$modelValue) ? scope.flightSearch.InfantCount.$modelValue : 0
									};

									scope.ActivateValidationSpinner = true;
									scope.PromoModel.Errors = null;
									haEcertAPI.isValidPromo(promoEligibilityRequest).success(function (results) {
										if (!!results && results.IsSuccess === false) {
											scope.ActivateValidationSpinner = false;
											scope.PromoModel.Errors = results.Errors;
											haModal(haConfig.getTemplateUrl('ha-booking-promo-modal.html'), {
												id: 'promoValidationModal',
												backdrop: 'true',
												scope: scope
											});
										} else {
											scope.ActivateValidationSpinner = false;
											$timeout(function () {
												//document.flightSearch.submit();
												submitFlightSearchRequest();
											});
										}
									})
										.error(function (err) {
											scope.ActivateValidationSpinner = false;
											if (err && err.RedirectURL) {
												window.location.href = err.RedirectURL;
											}
											return;
										});
								}
								else if (scope.promoCode != undefined && scope.promoCode.Code != undefined && scope.promoCode.Code != "" && !scope.promoCode.hasApplied) {
									$timeout(function () {
										haModal(haConfig.getTemplateUrl('ha-booking-promo-modal.html'), {
											id: 'promoValidationModal',
											backdrop: 'true',
											scope: scope
										});
									});
								}
								else {
									scope.$root.discountRemoved = true;
									scope.$root.selectedPromoId = null;
									$timeout(function () {
										//document.flightSearch.submit();
										submitFlightSearchRequest();
									});
								}
							}
							else {
								scope.ActivateValidationSpinner = false;
								$timeout(function () {
									//document.flightSearch.submit();
									submitFlightSearchRequest();
								});
							}
						});

					};

					// Submit form without promo
					scope.submitWithoutDiscount = function () {
						scope.$root.discountRemoved = true;
						scope.$root.selectedPromoId = null;
						clearPromoCodeInput();
						scope.$modalCancel();
						$timeout(function () {
							// document.flightSearch.submit();
							submitFlightSearchRequest();
						});
					};

					// Remove ETCO before submitting
					scope.submitWithoutETCO = function () {
						scope.etcoRemoved = true;
						scope.$modalCancel();
						$timeout(function () {
							//document.flightSearch.submit();
							submitFlightSearchRequest();
						});
					};

					// Recall Search
					scope.recallSearch = function (idx) {
						var flightQueryCookie = scope.flightQueryCookieArr[idx];
						apply(getSearchFromCookie(flightQueryCookie));
						scope.recent = false;
					};

					// Expand home page widget
					scope.setExpanded = function () {
						scope.expanded = true;
						//scope.scrollForm(scope.expanded);
						$('.booking-widget.row').addClass('open');
					};

					// Contract home page widget
					scope.setClosed = function () {
						scope.setTripType(2);
						scope.expanded = false;
						$('.booking-widget.row').removeClass('open');
					};

					// menu select for mobile
					scope.mobileSelect = function (tabName) {
						if (scope.tab === tabName) {
							scope.tab = '';
						} else {
							scope.tab = tabName;
						}
					};

					scope.openMobileRecentSearches = function () {
						haModal(haConfig.getTemplateUrl('/Book/FlightSearch/ha-recent-searches-modal.html'), {
							id: 'RecentSearchesModal',
							backdrop: 'true',
							extendScope: {
								flightQueryCookieArr: scope.flightQueryCookieArr,
								recallSearch: scope.recallSearch
							}
						});
					};

					function checkForAttributes() {
						// Set attributes
						['departureDate', 'arrivalDate', 'departureLocation', 'arrivalLocation']
							.forEach(function (prop) {
								scope[prop] = attrs[prop] || '';
							});

						// Default values for child and adult count
						scope.childrenCount = attrs.childrenCount || 0;
						scope.adultsCount = attrs.adultsCount || 1;

						scope.roundTripFlight = attrs.roundTripFlight != null && attrs.roundTripFlight === '1';
						// Set promo code value if attribute is set
						if (attrs.promoCodeValue) {
							scope.promoCode = {};
							scope.promoCode.Code = attrs.promoCodeValue;
						}
						// Check to see if any of the flight information attributes are set
						if (scope.departureDate || scope.arrivalDate || scope.departureLocation || scope.arrivalLocation || attrs.promoCodeValue) {
							scope.searchAttributesSet = true;
							return true;
						} else {
							scope.searchAttributesSet = false;
							return false;
						}
					}

					function getSearchFromAttributes() {
						var legs = [];
						var departureLeg = {
							origin: { code: scope.departureLocation.toUpperCase() },
							destination: { code: scope.arrivalLocation.toUpperCase() },
							departDate: scope.departureDate ? moment(scope.departureDate.substr(0, 10), "YYYY-MM-DD").toDate() : undefined
						};
						legs.push(departureLeg);
						// Check if it is round trip or one way
						if (scope.roundTripFlight === true) {
							var returnLeg = {
								origin: { code: scope.arrivalLocation.toUpperCase() },
								destination: { code: scope.departureLocation.toUpperCase() },
								departDate: scope.arrivalDate ? moment(scope.arrivalDate.substr(0, 10), "YYYY-MM-DD").toDate() : undefined
							};
							legs.push(returnLeg);
						}
						var search = buildQuery(legs, scope.adultsCount, scope.childrenCount);
						return search;
					};

					function getSearchFromQuery() {
						var legs = haUtils.parseLegs(haUtils.querystring('l'));
						return legs && buildQuery(legs, haUtils.querystring('a'), haUtils.querystring('c'));
					}

					//also used to process an item from the Recent Searches cookie
					function getSearchFromCookie(cookie) {
						if (!cookie) {
							return;
						}

						var legs = cookie.FlightSearchSegmentList.map(function (leg) {
							return {
								origin: { code: leg.OriginCityCode.toUpperCase() },
								destination: { code: leg.DestinationCityCode.toUpperCase() },
								departDate: moment(leg.DepartureDate.substr(0, 10), "YYYY-MM-DD").toDate(),
								miles: leg.IsMiles
							};
						});

						var search = buildQuery(legs, cookie.AdultCount, cookie.ChildCount);
						search.refundable = cookie.IsRefundable;
						return search;
					}

					function buildQuery(legs, adults, children) {
						// need to fix this to have an override if the attribute is set already.
						var isRoundTrip = legs.length === 2 && legs[0].origin.code === legs[1].destination.code;
						var noPassengers = !parseInt(adults, 10) && !parseInt(children, 10);
						return {
							legs: clearPastDates(legs),
							adults: noPassengers ? 1 : (parseInt(adults, 10) || 0),
							children: parseInt(children, 10) || 0,
							tripType: isRoundTrip ? 2 : (legs.length === 1 ? 1 : 0)
						};
					}

					function clearPastDates(legs) {
						// Ignore dates that are in the past if clearPastDates is set
						return legs.map(function (leg) {
							var departMoment = moment(leg.departDate);
							if (departMoment.diff(moment().startOf('day')) < 0) {
								leg.departDate = null;
							}
							return leg;
						});
					}

					function hideBookingTabs() {
						angular.element('a[href^="/book/flights"]').parent().addClass('ng-hide');
						angular.element('a[href^="/book/hotels"]').parent().addClass('ng-hide');
						angular.element('a[href^="/book/car-rentals"]').parent().addClass('ng-hide');
						angular.element('a[href^="/book/vacation-packages"]').parent().addClass('ng-hide');
						angular.element('a[href^="/book/activities-and-cruises"]').parent().addClass('ng-hide');
					}

					function showBookingTabs() {
						angular.element('a[href^="/book/flights"]').parent().removeClass('ng-hide');
						angular.element('a[href^="/book/hotels"]').parent().removeClass('ng-hide');
						angular.element('a[href^="/book/car-rentals"]').parent().removeClass('ng-hide');
						angular.element('a[href^="/book/vacation-packages"]').parent().removeClass('ng-hide');
						angular.element('a[href^="/book/activities-and-cruises"]').parent().removeClass('ng-hide');
					}

					function handlePromoCodeApply(promoCodePromotion) {
						scope.promoCode.isValidPromoCode = true;
						scope.promoCode.hasApplied = true;
						scope.promoCode.PromoCodePromotion = promoCodePromotion; // todo: lose this (new value) in the merge
						scope.PromoModel.Promo = promoCodePromotion;
						hideBookingTabs();
						$scs.get('Header.promoappliedmessage').then(function (data) {
							var markup = '<div ha-global-message header="' + data + '" type="success" message-close-time="4700"></div>';
							angular.element('[ha-global-header]').append($compile(markup)(scope));
						});
						scope.promoCodeNotFound = false;
						scope.$root.discountRemoved = false;
						scope.$root.discountPermanentlyRemoved = false;
						scope.$root.selectedPromoId = scope.PromoModel.Promo.OfferId;
					}

					function handlePromoCodeRemove() {
						scope.promoCode.isValidPromoCode = false;
						scope.promoCode.Code = null;
						showBookingTabs();
						// original behavior
						scope.$root.discountRemoved = true;
						scope.$root.discountPermanentlyRemoved = true;
						scope.$root.selectedPromoId = null
						// for promo codes: ajax call to server side method to remove promo code from session
						haEcertAPI.removePromoCodeFromSession();
					}

					function clearPromoCodeInput() {
						scope.promoCode.Code = null;
						scope.promoCodeNotFound = false;
						if (scope.flightSearch.PromoCode !== null && scope.flightSearch.PromoCode !== undefined) {
							scope.flightSearch.PromoCode.$setValidity('promoCodeNotFound', true);
							scope.flightSearch.PromoCode.$validate();
						}
					}

					function submitFlightSearchRequest() {
						var flightSearchQuery = buildSearchRedirectQuery();
						haHttpService.POST('/Book/Home/GetFlightSearchRedirectPath', flightSearchQuery).success(function (results) {
							if (!!results && results.Success === false) {
								$timeout(function () {
									document.flightSearch.submit();
								});
							} else {
								var data = results.RedirectUrl;

								//Get Adobe Visitor ID and append to redirect url
								if (typeof (Visitor) !== 'undefined') {
									let adobeVisitor = Visitor.getInstance("5E29123F5245B2B70A490D45@AdobeOrg", {
										trackingServer: "hawaiianairlines.sc.omtrdc.net",
										trackingServerSecure: "hawaiianairlines.sc.omtrdc.net",
										marketingCloudServer: "hawaiianairlines.sc.omtrdc.net",
										marketingCloudServerSecure: "hawaiianairlines.sc.omtrdc.net"
									});
									data = adobeVisitor.appendVisitorIDsTo(data);
								}

								if (typeof data === "string" && data !== "") {
									window.location.href = data;
								}
								else {
									document.flightSearch.submit();
								}
							}
						}).error(function (err) {
							document.flightSearch.submit();
						});
					}

					function buildSearchRedirectQuery() {
						var searchQuery = {};
						var legs = getLegs(true);
						searchQuery.tripType = scope.tripType;
						searchQuery.pricingType = scope.paymentType.type;
						searchQuery.isRefundable = scope.flightSearch.IsRefundableCheck;
						searchQuery.isflexibleDatesCalendar = scope.flightSearch.flexibleDatesCalendar !== undefined ? scope.flightSearch.flexibleDatesCalendar.$viewValue : false;
						searchQuery.segments = [];
						for (var i = 0; i < legs.length; i++) {
							var leg = {};
							leg.originCityCode = legs[i].origin.code;
							leg.destinationCityCode = legs[i].destination.code;
							leg.departureDate = moment(legs[i].departDate).format("YYYY-MM-DD");
							searchQuery.segments.push(leg);
						}
						searchQuery.promoCode = scope.promoCode !== undefined ? scope.promoCode.Code : scope.flightSearch.PromoCode != undefined ? scope.flightSearch.PromoCode.$modelValue : "";
						searchQuery.currency = scope.$currency;
						searchQuery.adultCount = scope.pax.adultCount;
						searchQuery.childCount = scope.pax.childCount;
						return searchQuery;
					}

					// INITIALIZATION
					//*******************
					function initialize() {

						// Promo Model
						haGlobals('promoJson', function (promoJson) {
							scope.PromoModel = promoJson || undefined;  // todo: old, stays in the merge and new html refers to this

							scope.promoCode = {};// todo: new, goes away in the merge and nobody uses
							scope.promoCodeNotFound = false;// todo: new, goes away in the merge and nobody uses
							scope.promoCode.PromoCodePromotion = scope.PromoModel.Promo; // todo: new, goes away in the merge and nobody uses
							if (scope.PromoModel !== null && scope.PromoModel.Promo !== null) {
								scope.$root.selectedPromoId = scope.PromoModel.Promo.OfferId;
								if (scope.PromoModel.Promo.OfferType === 10) {
									handlePromoCodeApply(scope.PromoModel.Promo);
								}
							}
						});

						haGlobals('enableTCR', function (enableTCR) {
							scope.enableTCR = enableTCR;
						});

						haGlobals('wholesaleDisableDollarMile', function (wholesaleDisableDollarMile) {
							scope.wholesaleDisableDollarMile = wholesaleDisableDollarMile;
						});

						haGlobals('ETCOResponseModel', function (etcoResponseModel) {
							scope.ETCOResponseModel = etcoResponseModel;
						});
						haGlobals('flightSearchModel', function (flightSearchModel) {
							scope.disableAdultFieldForNITP = flightSearchModel.DisableAdultField;
						});
						haGlobals('widgetDatepickerAlertIcon', function (widgetDatepickerAlertIcon) {
							scope.widgetDatepickerAlertIcon = widgetDatepickerAlertIcon;
						});

						scope.ActivateValidationSpinner = false;
						scope.formSubmitted = false;

						// Booking Form Options
						setAttributes();

						// Market
						scope.intl = false;
						scope.isEN = haUtils.isEN();

						// Legs
						scope.legs = [];
						var leg = {};
						scope.legs.push(leg);

						// Dates
						scope.departDate = [];
						scope.returnDate = [];
						scope.currentDateChoice = '';

						// Calendar
						scope.calendarOpen = [];
						scope.messages = [];

						// Unavailable Days
						scope.unavailableDays = [];

						// Passenger stuff
						scope.pax = {};
						scope.passengerCount = [0, 1, 2, 3, 4, 5, 6, 7];
						scope.childrenOnly = false;

						scope.isAffiliate = false;
						if (scope.$root.user && scope.$root.user.accountType == 'A') {
							scope.isAffiliate = true;
						}

						scope.isCorporate = scope.$root.isLoggedIn && scope.$root.corpAccTypes.indexOf(scope.$root.user.accountType) > -1;

						// Miles radio button
						scope.paymentType = {};

						scope.enablepricechart = scope.$switch('LowFare:enablepricechart');
						scope.enablepricecalendar = scope.$switch('LowFare:enablepricecalendar');

						if (scope.ETCOResponseModel) {
							$scs.get('ReservationsTravelCreditRedemption.offallroutesfootertext').then(function (txt) {
								var amount = $filter('localCurrency')(scope.ETCOResponseModel.Amount, scope.$currency);
								scope.offAllRoutesFooterText = txt.replace('{{ETCO.Dollar.Discounts}}', amount);
							});
						}

						var autoOpen = window.location.href.toLowerCase().indexOf('flexibleautoopen') !== -1;
						var search;

						// If autoopen url param set, ensure that the cookie can NOT be used
						if (!autoOpen) {
							search = checkForAttributes() ? getSearchFromAttributes() : getSearchFromQuery() || getSearchFromCookie(haUtils.getFlightQueryModelCookie());
						} else {
							search = getSearchFromAttributes() || getSearchFromQuery();
						}

						scope.promoCodeMaxLength = 15;

						var promoCodeToApplyInUrl = haUtils.querystring('pcodeapply');

						if (promoCodeToApplyInUrl && promoCodeToApplyInUrl.length > 0) {

							scope.promoCode.Code = promoCodeToApplyInUrl.substring(0, scope.promoCodeMaxLength);

							if (scope.$root.$regex.promoCode.test(scope.promoCode.Code)) {
								scope.applyPromoCode();
							}
						}
						else {
							var promoCodeInUrl = haUtils.querystring('pcode');
							if (promoCodeInUrl && promoCodeInUrl.length > 0) {
								scope.promoCode.Code = promoCodeInUrl.substring(0, scope.promoCodeMaxLength);
							}
						}
						var promoCodeUrl = haUtils.querystring('promoCode');
						if (promoCodeUrl && promoCodeUrl.length > 0) {
							scope.promoCode = {};
							scope.promoCode.Code = promoCodeUrl.substring(0, scope.promoCodeMaxLength);
						}
						$timeout(function () {
							if (scope.searchAttributesSet) {
								applyWithAttributes(search);
							} else {
								apply(search, true);
							}
							processRecentSearches();
							scope.$broadcast('trip type changed', scope.tripType);
						}, 0);

						if (autoOpen) {
							//getcities getscalled twise, so protect against two instances
							scope.autoOpened = false;
							scope.$on('citiesavailable', function () {
								if (!scope.autoOpened) {
									scope.autoOpened = true;
									$timeout(function () {
										scope.openPriceChart();
									});
								}
							});
						}


						scope.ready = true;
						$(window).on('load', function () {
							const urlParams = new URLSearchParams(window.location.search);
							const myParam = urlParams == null ? false : urlParams.get('multiCitySearchError');
							scope.isContiguousError = false;
							scope.isNoFlightsError = urlParams == null ? false : urlParams.get('noFlightsFound');
							if (myParam == 'true') {
								scope.setTripType(0);
								scope.focusTripTypeSelection();
								scope.isContiguousError = true;
							}
						});

						// Lazy load flight results strings if routed through Akamai
						if (HA.cdnDynamic) {
							$(window).on('load', function () {
								['BookingWidget', 'StickProgressBar', 'LowFare', 'InflightOptions', 'InFlightOptionsInfo', 'PassengerTripSummary', 'FareSearch', 'PROMO_DISCOUNTS', 'ETCO_CHECK'].map($scs.request);
							});
						}

					}

					initialize();
				}
			};
		}]);

	// DATE INPUT FIELDS
	//module.directive('haBookingDateInput', ['haTemplateCache', 'haConfig', function (haTemplateCache, haConfig) {

	//	// Preload
	//	haTemplateCache.get(haConfig.getTemplateUrl('ha-calendar2.html'));
	//	haTemplateCache.get(haConfig.getTemplateUrl('ha-booking-day-template.html'));
	//	haTemplateCache.get(haConfig.getTemplateUrl('Book/FlightResults/ha-flightresults-selectedsegments.html'));
	//	haTemplateCache.get(haConfig.getTemplateUrl('Book/FlightResults/ha-flightresults-currentsegment.html'));
	//	haTemplateCache.get(haConfig.getTemplateUrl('Book/FlightResults/ha-flightresults-itemendonendwithprice.html'));
	//	haTemplateCache.get(haConfig.getTemplateUrl('Book/FlightResults/ha-flightresults-faregrid.html'));

	//	return {
	//		templateUrl: haConfig.getTemplateUrl('ha-booking-date-input.html'),
	//		scope: {
	//			inputModel: '=',
	//			calendarOpen: '=',
	//			currentDateChoice: '=',
	//			inputName: '@',
	//			displayName: '@',
	//			label: '@',
	//			placeholder: '@',
	//			id: '@',
	//			disabled: '=',
	//			ngRequired: '=',
	//			errorMessage: '@',
	//			idx: '='
	//		},
	//		link: function (scope, element) {

	//			var called = false;	// This is because focus is called twice - TODO - figure out why
	//			//if no displayName passed through, just create one.
	//			scope.displayName = scope.displayName ? scope.displayName : scope.inputName + 'Display';
	//			scope.onFocus = function () {

	//				if (!called) {
	//					scope.currentDateChoice = (scope.id).indexOf('departDate') >= 0 ? 'departDate' : 'returnDate';
	//					scope.$emit('dateInputFocused', scope.currentDateChoice, scope.idx);
	//					called = true;
	//				}
	//			};

	//			scope.onBlur = function () {
	//				scope.$emit('dateInputBlurred', element);
	//				called = false;
	//			};

	//			scope.isActive = function () {
	//				return (scope.id.indexOf(scope.currentDateChoice) > -1 && scope.calendarOpen[scope.idx]);

	//			};
	//		}
	//	};
	//}]);

	// FORMATTER FOR DATE INPUTS
	module.directive('haBookingDateFormatter', ['$filter', '$locale', function ($filter, $locale) {
		return {
			require: 'ngModel',
			link: function (scope, element, attrs, ngModelCtrl) {
				ngModelCtrl.$formatters.push(function (value) {
					if (!(value) || !(value instanceof Date)) {
						return '';
					}
					return $filter('date')(value, 'EEEE, ') + $filter('date')(value, $locale.DATETIME_FORMATS.shortDate);
				});
			}
		};
	}]);

	module.directive('haDatesAvailable', ['haUnavailableDays', function (haUnavailableDays) {
		return {
			restrict: 'A',
			require: 'ngModel',
			link: function ($scope, $el, $attrs, ngModel) {

				var validate = function (viewValue, unavailableDays) {

					var isValid = true;

					if (!unavailableDays || !unavailableDays.length) {
						//if there is no unavailableDays array, just return valid
						ngModel.$setValidity('haDates', isValid);
						return isValid;
					}

					if (ngModel.$modelValue) {
						if ($scope.tripType !== 1) {
							isValid = !haUnavailableDays.isUnavailable(ngModel.$modelValue, unavailableDays, 'departDate') || !haUnavailableDays.isUnavailable(ngModel.$modelValue, unavailableDays, 'returnDate');
						} else {
							isValid = !haUnavailableDays.isUnavailable(ngModel.$modelValue, unavailableDays, 'departDate');
						}
					}


					ngModel.$setValidity('haDates', isValid);

					return viewValue;
				};

				ngModel.$formatters.push(validate);

				$scope.$on('unavailableDaysChanged', function (e, unavailableDays) {
					return validate(ngModel.$viewValue, unavailableDays);
				});
			}
		};
	}]);

	module.factory('haUnavailableDays', ['haHttpService', '$q', function (haHttpService, $q) {
		var unavailable = {};

		function contains(date) {
			/*jshint validthis:true */
			var data = this; //jscs:ignore safeContextKeyword
			return (data = data && data[date.getFullYear()]) && (data = data[date.getMonth() + 1]) && data[date.getDate()];
		}

		function getUnavailableDays(pair, type) {
			pair = pair.split('-');
			return haHttpService.GET('/book/flightschedule/UnAvailableDaysOfOperation', {
				method: 'get',
				params: {
					origin: pair[0],
					destination: pair[1],
					triptype: type || 1
				},
				config: { cache: true }
			});
		}

		return {
			get: function (pair) {
				var promise = unavailable[pair];
				if (promise) {
					return promise;
				}

				return (unavailable[pair] = getUnavailableDays(pair)
					.then(function (response) {
						var data = response.data[pair] || { CalendarYears: {} };
						data.CalendarYears.pair = pair;
						data.CalendarYears.contains = contains;
						return data.CalendarYears;
					})
					.catch(function (err) {
						console.log(err);
						return Promise.reject(err);
					}));
			},

			//legacy code for old datepickers...
			getUnavailableDays: function (leg, $scope) {
				return $q.all([
					this.get(pair(leg), true),
					this.get(pair(leg, true), true)
				])
					.then(function (unavailableDays) {
						unavailableDays = unavailableDays.map(function (ud) {
							return { CalendarYears: ud };
						});
						$scope.$broadcast('unavailableDaysChanged', unavailableDays);
						return unavailableDays;//pass to the next in the chain
					});
			},

			isUnavailable: function (date, unavailableDays, currentDateChoice) {

				if (!unavailableDays || !unavailableDays.length) {
					return;
				}
				//check unavailableDays array
				var ud = (currentDateChoice === 'departDate') ? unavailableDays[0] : unavailableDays[1];
				return ud.CalendarYears.contains(date);
			}
		};
	}]);

	// CLICK DELEGATE DIRECTIVE
	module.directive('haDelegateClick', function () {
		return function ($scope, element, attrs) {
			var fn = attrs.haDelegateClick;
			element.on('click', attrs.haDelegateSelector, function (e) {
				var $target = $(e.target);
				var $dataElement = $target.data('ng-json') ? $target : $target.closest('[data-ng-json]');
				var data = angular.fromJson($dataElement.data('ngJson') || undefined);

				if (typeof $scope[fn] === 'function') {
					$scope[fn](e, data);
				}
			});
		};
	});

	//get an airport pair string from a leg. e.g. "LAX-HNL"
	function pair(leg, reverse) {
		if (!leg || !leg.origin || !leg.origin.Code || !leg.destination || !leg.destination.Code) {
			return;
		}
		return reverse ? (leg.destination.Code + '-' + leg.origin.Code) : (leg.origin.Code + '-' + leg.destination.Code);
	}
})(angular);
;
(function (angular) {

	'use strict';

	var module = angular.module('haFlexiblePriceViewModule', ['haPriceApiModule', 'ngAnimate', 'haFeatureFlagsModule']);

	// Flag to keep track of animation status
	var animating = false;
	var CEILING_FACTOR = 0.33;            // Proportion of max price to pad the top of the chart with
	var MAX_DAYS = 331;                   // How far into the future we can scroll
	var VISIBLE_DAYS_COUNT = 25;          // How many days are shown at a time.
	var MOVE_TIMEOUT_MILLESECONDS = 1000; // How long to wait before assuming the user is done moving.
	var moveTimeout;                      // Holds the timeout object
	var monthKeys = ['januarytext', 'februarytext', 'marchtext', 'apriltext', 'maytext', 'junetext', 'julytext', 'augusttext', 'septembertext', 'octobertext', 'novembertext', 'decembertext'];


	module.directive('haFlexiblePriceView', [
		'haFeatureFlags',
		'haSitecoreStrings',
		'haHttpService',
		'haPriceApiService',
		'haConfig',
		'haDateUtils',
		'$filter',
		'$animate',
		'haUtils',
		'$timeout',
		'$rootScope',
		function (haFeatureFlags, $scs, http, priceApi, haConfig, haDateUtils, $filter, $animate, haUtils, $timeout, $rootScope) {
			return {
				templateUrl: haConfig.getTemplateUrl('/Book/FlightSearch/ha-flexible-price-view.html'),
				restrict: 'A',
				link: function ($scope, element) {

					// FEATURE FLAGS
					//*******************

					// EVENT HANDLERS
					//*******************
					// Calendar forward/backward
					$scope.$on('calendarForward', function () {
						$scope.model.loading = true;
						calendarAction(1);
					});
					$scope.$on('calendarBackward', function () {
						$scope.model.loading = true;
						calendarAction(-1);

					});

					// WATCHERS
					//*******************
					// Watch tripLength
					$scope.$watch('model.tripLength', function (nv, ov) {
						if (nv !== ov) {
							$scope.legs[1].departDate = moment($scope.legs[0].departDate).add(nv, 'days').toDate();
							$scope.maxForward = {}; // reset the value as the trip length has changed
							$scope.updatePrices();
						}
					});

					// Watch Ajax results for initial load
					$scope.initialLoad = true;
					$scope.$watch('results', function (nv) {
						if (nv && nv.length) {
							$scope.initialLoad = false;
							$animate.enabled(false);
							//resetDates();
							addResultsToBuffer(nv);
							refreshData();
							updateMonths();
							if ($rootScope.isMobile) {
								refreshBoundariesMobile();
							}

						}
					});

					// LOCAL FUNCTIONS
					//*******************
					// Build JSON for form POST
					function buildRequestJSON() {

						//MOCK DATA///////////////////////////////////////////////
						//var unavailableDaysObj1 = { "PPT-HNL": { "Origin": "PPT", "Destination": "HNL", "CalendarYears": { "2015": { "3": { "9": 1, "10": 1, "11": 1, "12": 1, "13": 1, "15": 1, "16": 1, "17": 1, "18": 1, "19": 1, "20": 1, "22": 1, "23": 1, "24": 1, "25": 1, "26": 1, "27": 1, "29": 1, "30": 1 }, "4": { "1": 1, "2": 1, "3": 1, "5": 1, "6": 1, "8": 1, "9": 1, "10": 1, "12": 1, "13": 1, "14": 1, "15": 1, "16": 1, "17": 1, "19": 1, "20": 1, "21": 1, "22": 1, "23": 1, "24": 1, "26": 1, "27": 1, "28": 1, "29": 1, "30": 1 }, "5": { "1": 1, "3": 1, "4": 1, "5": 1, "6": 1, "7": 1, "8": 1, "10": 1, "11": 1, "12": 1, "13": 1, "14": 1, "15": 1, "17": 1, "18": 1, "19": 1, "20": 1, "21": 1, "22": 1, "24": 1, "25": 1, "26": 1, "27": 1, "28": 1, "29": 1, "31": 1 }, "6": { "1": 1, "2": 1, "3": 1, "4": 1, "5": 1, "7": 1, "8": 1, "9": 1, "10": 1, "11": 1, "12": 1, "14": 1, "15": 1, "16": 1, "17": 1, "18": 1, "19": 1, "21": 1, "22": 1, "23": 1, "24": 1, "25": 1, "26": 1, "28": 1, "29": 1, "30": 1 }, "7": { "1": 1, "2": 1, "3": 1, "5": 1, "6": 1, "7": 1, "8": 1, "9": 1, "10": 1, "12": 1, "13": 1, "14": 1, "15": 1, "16": 1, "17": 1, "19": 1, "20": 1, "21": 1, "22": 1, "23": 1, "24": 1, "26": 1, "27": 1, "28": 1, "29": 1, "30": 1, "31": 1 }, "8": { "2": 1, "3": 1, "4": 1, "5": 1, "6": 1, "7": 1, "9": 1, "10": 1, "11": 1, "12": 1, "13": 1, "14": 1, "16": 1, "17": 1, "18": 1, "19": 1, "20": 1, "21": 1, "23": 1, "24": 1, "25": 1, "26": 1, "27": 1, "28": 1, "30": 1, "31": 1 }, "9": { "1": 1, "2": 1, "3": 1, "4": 1, "6": 1, "7": 1, "8": 1, "9": 1, "10": 1, "11": 1, "13": 1, "14": 1, "15": 1, "16": 1, "17": 1, "18": 1, "20": 1, "21": 1, "22": 1, "23": 1, "24": 1, "25": 1, "27": 1, "28": 1, "29": 1, "30": 1 }, "10": { "1": 1, "2": 1, "4": 1, "5": 1, "6": 1, "7": 1, "8": 1, "9": 1, "11": 1, "12": 1, "13": 1, "14": 1, "15": 1, "16": 1, "18": 1, "19": 1, "20": 1, "21": 1, "22": 1, "23": 1, "25": 1, "26": 1, "27": 1, "28": 1, "29": 1, "30": 1 }, "11": { "1": 1, "2": 1, "3": 1, "4": 1, "5": 1, "6": 1, "8": 1, "9": 1, "10": 1, "11": 1, "12": 1, "13": 1, "15": 1, "16": 1, "17": 1, "18": 1, "19": 1, "20": 1, "22": 1, "23": 1, "24": 1, "25": 1, "26": 1, "27": 1, "29": 1, "30": 1 }, "12": { "1": 1, "2": 1, "3": 1, "4": 1, "6": 1, "7": 1, "8": 1, "9": 1, "10": 1, "11": 1, "13": 1, "14": 1, "15": 1, "16": 1, "17": 1, "18": 1, "20": 1, "21": 1, "22": 1, "23": 1, "24": 1, "25": 1, "27": 1, "28": 1, "29": 1, "30": 1, "31": 1 } }, "2016": { "1": { "1": 1, "3": 1, "4": 1, "5": 1, "6": 1, "7": 1, "8": 1, "10": 1, "11": 1, "12": 1, "13": 1, "14": 1, "15": 1, "17": 1, "18": 1, "19": 1, "20": 1, "21": 1, "22": 1 } } } }, "HNL-PPT": { "Origin": "HNL", "Destination": "PPT", "CalendarYears": { "2015": { "3": { "9": 1, "10": 1, "11": 1, "12": 1, "13": 1, "15": 1, "16": 1, "17": 1, "18": 1, "19": 1, "20": 1, "22": 1, "23": 1, "24": 1, "25": 1, "26": 1, "27": 1, "29": 1, "30": 1 }, "4": { "1": 1, "2": 1, "3": 1, "5": 1, "6": 1, "8": 1, "9": 1, "10": 1, "12": 1, "13": 1, "14": 1, "15": 1, "16": 1, "17": 1, "19": 1, "20": 1, "21": 1, "22": 1, "23": 1, "24": 1, "26": 1, "27": 1, "28": 1, "29": 1, "30": 1 }, "5": { "1": 1, "3": 1, "4": 1, "5": 1, "6": 1, "7": 1, "8": 1, "10": 1, "11": 1, "12": 1, "13": 1, "14": 1, "15": 1, "17": 1, "18": 1, "19": 1, "20": 1, "21": 1, "22": 1, "24": 1, "25": 1, "26": 1, "27": 1, "28": 1, "29": 1, "31": 1 }, "6": { "1": 1, "2": 1, "3": 1, "4": 1, "5": 1, "7": 1, "8": 1, "9": 1, "10": 1, "11": 1, "12": 1, "14": 1, "15": 1, "16": 1, "17": 1, "18": 1, "19": 1, "21": 1, "22": 1, "23": 1, "24": 1, "25": 1, "26": 1, "28": 1, "29": 1, "30": 1 }, "7": { "1": 1, "2": 1, "3": 1, "5": 1, "6": 1, "7": 1, "8": 1, "9": 1, "10": 1, "12": 1, "13": 1, "14": 1, "15": 1, "16": 1, "17": 1, "19": 1, "20": 1, "21": 1, "22": 1, "23": 1, "24": 1, "26": 1, "27": 1, "28": 1, "29": 1, "30": 1, "31": 1 }, "8": { "2": 1, "3": 1, "4": 1, "5": 1, "6": 1, "7": 1, "9": 1, "10": 1, "11": 1, "12": 1, "13": 1, "14": 1, "16": 1, "17": 1, "18": 1, "19": 1, "20": 1, "21": 1, "23": 1, "24": 1, "25": 1, "26": 1, "27": 1, "28": 1, "30": 1, "31": 1 }, "9": { "1": 1, "2": 1, "3": 1, "4": 1, "6": 1, "7": 1, "8": 1, "9": 1, "10": 1, "11": 1, "13": 1, "14": 1, "15": 1, "16": 1, "17": 1, "18": 1, "20": 1, "21": 1, "22": 1, "23": 1, "24": 1, "25": 1, "27": 1, "28": 1, "29": 1, "30": 1 }, "10": { "1": 1, "2": 1, "4": 1, "5": 1, "6": 1, "7": 1, "8": 1, "9": 1, "11": 1, "12": 1, "13": 1, "14": 1, "15": 1, "16": 1, "18": 1, "19": 1, "20": 1, "21": 1, "22": 1, "23": 1, "25": 1, "26": 1, "27": 1, "28": 1, "29": 1, "30": 1 }, "11": { "1": 1, "2": 1, "3": 1, "4": 1, "5": 1, "6": 1, "8": 1, "9": 1, "10": 1, "11": 1, "12": 1, "13": 1, "15": 1, "16": 1, "17": 1, "18": 1, "19": 1, "20": 1, "22": 1, "23": 1, "24": 1, "25": 1, "26": 1, "27": 1, "29": 1, "30": 1 }, "12": { "1": 1, "2": 1, "3": 1, "4": 1, "6": 1, "7": 1, "8": 1, "9": 1, "10": 1, "11": 1, "13": 1, "14": 1, "15": 1, "16": 1, "17": 1, "18": 1, "20": 1, "21": 1, "22": 1, "23": 1, "24": 1, "25": 1, "27": 1, "28": 1, "29": 1, "30": 1, "31": 1 } }, "2016": { "1": { "1": 1, "3": 1, "4": 1, "5": 1, "6": 1, "7": 1, "8": 1, "10": 1, "11": 1, "12": 1, "13": 1, "14": 1, "15": 1, "17": 1, "18": 1, "19": 1, "20": 1, "21": 1, "22": 1 } } } } };
						//var unavailableDaysObj2 = { "BNE-HNL": { "Origin": "BNE", "Destination": "HNL", "CalendarYears": { "2015": { "3": { "9": 1, "11": 1, "13": 1, "16": 1, "18": 1, "20": 1, "23": 1, "25": 1, "27": 1, "30": 1 }, "4": { "1": 1, "3": 1, "6": 1, "8": 1, "10": 1, "13": 1, "15": 1, "17": 1, "20": 1, "22": 1, "24": 1, "27": 1, "29": 1 }, "5": { "1": 1, "4": 1, "6": 1, "8": 1, "11": 1, "13": 1, "15": 1, "18": 1, "20": 1, "22": 1, "25": 1, "27": 1, "29": 1 }, "6": { "1": 1, "3": 1, "5": 1, "8": 1, "10": 1, "12": 1, "15": 1, "17": 1, "19": 1, "22": 1, "24": 1, "26": 1, "29": 1 }, "7": { "1": 1, "3": 1, "6": 1, "8": 1, "10": 1, "13": 1, "15": 1, "17": 1, "20": 1, "22": 1, "24": 1, "27": 1, "29": 1, "31": 1 }, "8": { "3": 1, "5": 1, "7": 1, "10": 1, "12": 1, "14": 1, "17": 1, "19": 1, "21": 1, "24": 1, "26": 1, "28": 1, "31": 1 }, "9": { "2": 1, "4": 1, "7": 1, "9": 1, "11": 1, "14": 1, "16": 1, "18": 1, "21": 1, "23": 1, "25": 1, "28": 1, "30": 1 }, "10": { "2": 1, "5": 1, "7": 1, "9": 1, "12": 1, "14": 1, "16": 1, "19": 1, "21": 1, "23": 1, "26": 1, "28": 1, "30": 1 }, "11": { "2": 1, "4": 1, "6": 1, "9": 1, "11": 1, "13": 1, "16": 1, "18": 1, "20": 1, "23": 1, "25": 1, "27": 1, "30": 1 }, "12": { "2": 1, "4": 1, "7": 1, "9": 1, "11": 1, "14": 1, "16": 1, "18": 1, "21": 1, "23": 1, "25": 1, "28": 1, "30": 1 } }, "2016": { "1": { "1": 1, "4": 1, "6": 1, "8": 1, "11": 1, "13": 1, "15": 1, "18": 1, "20": 1, "22": 1 } } } }, "HNL-BNE": { "Origin": "HNL", "Destination": "BNE", "CalendarYears": { "2015": { "3": { "10": 1, "12": 1, "15": 1, "17": 1, "19": 1, "22": 1, "24": 1, "26": 1, "29": 1, "31": 1 }, "4": { "2": 1, "5": 1, "7": 1, "9": 1, "12": 1, "14": 1, "16": 1, "19": 1, "21": 1, "23": 1, "26": 1, "28": 1, "30": 1 }, "5": { "3": 1, "5": 1, "7": 1, "10": 1, "12": 1, "14": 1, "17": 1, "19": 1, "21": 1, "24": 1, "26": 1, "28": 1, "31": 1 }, "6": { "2": 1, "4": 1, "7": 1, "9": 1, "11": 1, "14": 1, "16": 1, "18": 1, "21": 1, "23": 1, "25": 1, "28": 1, "30": 1 }, "7": { "2": 1, "5": 1, "7": 1, "9": 1, "12": 1, "14": 1, "16": 1, "19": 1, "21": 1, "23": 1, "26": 1, "28": 1, "30": 1 }, "8": { "2": 1, "4": 1, "6": 1, "9": 1, "11": 1, "13": 1, "16": 1, "18": 1, "20": 1, "23": 1, "25": 1, "27": 1, "30": 1 }, "9": { "1": 1, "3": 1, "6": 1, "8": 1, "10": 1, "13": 1, "15": 1, "17": 1, "20": 1, "22": 1, "24": 1, "27": 1, "29": 1 }, "10": { "1": 1, "4": 1, "6": 1, "8": 1, "11": 1, "13": 1, "15": 1, "18": 1, "20": 1, "22": 1, "25": 1, "27": 1, "29": 1 }, "11": { "1": 1, "3": 1, "5": 1, "8": 1, "10": 1, "12": 1, "15": 1, "17": 1, "19": 1, "22": 1, "24": 1, "26": 1, "29": 1 }, "12": { "1": 1, "3": 1, "6": 1, "8": 1, "10": 1, "13": 1, "15": 1, "17": 1, "20": 1, "22": 1, "24": 1, "27": 1, "29": 1, "31": 1 } }, "2016": { "1": { "3": 1, "5": 1, "7": 1, "10": 1, "12": 1, "14": 1, "17": 1, "19": 1, "21": 1 } } } } };

						////Set depart
						//$scope.unavailableDays[0] = unavailableDaysObj2[$scope.legs[0].origin.Code + '-' + $scope.legs[0].destination.Code];
						////Set return
						//$scope.unavailableDays[1] = unavailableDaysObj2[$scope.legs[0].destination.Code + '-' + $scope.legs[0].origin.Code];
						//END MOCK DATA///////////////////////////////////////////////

						var legs = $scope.legs;
						var midRangeDepart = new Date(legs[0].departDate);
						var midRangeReturn = new Date(legs[0].departDate).dateAdd('day', $scope.model.tripLength);

						function getSegment(i, depart) {
							return {
								OriginCityCode: legs[i].origin.code,
								DestinationCityCode: legs[i].destination.code,
								DepartureDate: moment(depart).format('YYYY-MM-DD'),
								SegmentID: i + 1
							};
						}


						var requestJSON = {
							searchRequest: {
								FlightQueryTypeId: $scope.legs.length,
								AdultCount: $scope.adults,
								ChildCount: $scope.children,
								FlightSearchSegmentList: [
									getSegment(0, midRangeDepart)
								]
							},
							isCalendar: $scope.display === 'calendar'
						};
						if ($scope.legs.length === 2) {
							requestJSON.searchRequest.FlightSearchSegmentList.push(getSegment(1, midRangeReturn));
						}

						return requestJSON;
					}

					// Set the Current Date value
					// (lowest visible value = (Selected Date - 12 [half of 25 visible])
					function resetDates() {
						$scope.selectedPrice = {
							date: new Date($scope.legs[0].departDate),
							price: 0
						};
						if ($rootScope.isMobile) {
							$scope.initialPrice = {
								date: new Date($scope.legs[0].departDate),
								price: 0
							};
						}

						// For calendar, set date to 1st day of the month in which the depart date occurs
						// For chart, it's selected date minus 12 days (since the chart shows 12 days on each side of the selected date)
						if ($scope.display === 'chart') {
							$scope.currentDate = new Date($scope.selectedPrice.date);
							$scope.currentDate.setDate($scope.currentDate.getDate() - parseInt(VISIBLE_DAYS_COUNT / 2));
						} else {
							$scope.currentDate = new Date($scope.legs[0].departDate.getFullYear(), $scope.legs[0].departDate.getMonth(), 1);
						}
					}

					// NOTE: resultsArray: [{Price: <number>, DepartDate: <String> }]
					function addResultsToBuffer(resultsArray) {

						// for mobile, only show mostly complete months
						if ($rootScope.isMobile && resultsArray.length > 90) {

							var completeMonths = [];
							// find complete months (have at least 20 days)
							for (var i = 0, len = resultsArray.length; i < len; i++) {
								var dateString = resultsArray[i].DepartDate;

								var mo = moment(dateString).month();
								if (moment(dateString).date() >= 20 && completeMonths.indexOf(mo) < 0) {
									completeMonths.push(mo);
								}
							}
							// purge partial months
							for (var i = 0; i < resultsArray.length; i++) {
								var dateString = resultsArray[i].DepartDate;
								if (completeMonths.indexOf(moment(dateString).month()) < 0) {
									resultsArray.splice(i, 1);
									i--;
								}
							}

						}

						//if first time through, init.  If not, set
						$scope.firstBufferDate = $scope.firstBufferDate ? $scope.firstBufferDate : new Date('1/1/2100');
						$scope.lastBufferDate = $scope.lastBufferDate ? $scope.lastBufferDate : new Date('1/1/1900');

						for (var i = 0, len = resultsArray.length; i < len; i++) {
							var result = resultsArray[i];
							var depart = result.DepartDate;
							if (depart === $scope.selectedPrice.date.YYYY_MM_DD()) {
								$scope.selectedPrice.price = result.Price;
							}
							if ($rootScope.isMobile && depart === $scope.initialPrice.date.YYYY_MM_DD()) {
								$scope.initialPrice.price = result.Price;
							}
							$scope.buffer[depart] = {
								price: result.Price,
								departDate: depart,
								returnDate: result.ReturnDate
							};

							depart = moment(result.DepartDate).toDate();
							//make sure as we look through the set that the lastBufferDate/firstBufferDate is the latest/earliest date.
							$scope.lastBufferDate = haDateUtils.isAfter(depart, $scope.lastBufferDate) ? depart : $scope.lastBufferDate;
							$scope.firstBufferDate = haDateUtils.isBefore(depart, $scope.firstBufferDate) ? depart : $scope.firstBufferDate;

						}
					}

					// Build Chart and Calendar Data Models
					function refreshData() {
						var tempDate;
						var departDate;
						var returnDate;
						var price;

						$scope.chartData = [];  //chartData is an array
						$scope.calendarData = {}; //calendarData is Dictionary

						// Reset min/max
						$scope.minPrice = Number.POSITIVE_INFINITY;
						$scope.maxPrice = Number.NEGATIVE_INFINITY;

						// Build chart data and calendar data
						var dataRange;

						if ($rootScope.isMobile) {
							// If mobile, show the whole buffer
							dataRange = haDateUtils.numDaysDifference(new Date($scope.firstBufferDate), new Date($scope.lastBufferDate));

						} else {
							// We want to loop from the first visible day to the last day of the buffer.
							dataRange = haDateUtils.numDaysDifference($scope.currentDate, new Date($scope.lastBufferDate));
							//if we are the end of the buffer, use visible
							dataRange = (dataRange > VISIBLE_DAYS_COUNT) ? dataRange : VISIBLE_DAYS_COUNT;
						}
						for (var i = 0; i < dataRange + 1; i++) {
							// If mobile, build from the first buffer date, otherwise use currentDate
							var baseDate = $rootScope.isMobile ? $scope.firstBufferDate : $scope.currentDate;
							tempDate = new Date(baseDate);
							tempDate.setDate(baseDate.getDate() + i);
							returnDate = new Date(tempDate);
							returnDate.setDate(tempDate.getDate() + parseInt($scope.model.tripLength));
							departDate = tempDate;
							price = undefined;  //reset price

							if (departDate.YYYY_MM_DD() === $scope.selectedPrice.date.YYYY_MM_DD()) {
								if ($scope.buffer[tempDate.YYYY_MM_DD()] && $scope.buffer[tempDate.YYYY_MM_DD()].price) {
									$scope.selectedPrice.price = $scope.buffer[tempDate.YYYY_MM_DD()].price;
								} else {
									$scope.selectionError = true;
								}
								//$scope.selectedPrice.price = $scope.buffer[tempDate.YYYY_MM_DD()].price;
							}

							if ($scope.buffer[departDate.YYYY_MM_DD()]) {
								price = $scope.buffer[departDate.YYYY_MM_DD()].price;
								if (price) {
									// Calculate min/max price in this pass, but only for visible days on the chart
									if (i <= VISIBLE_DAYS_COUNT || $rootScope.isMobile) {
										$scope.minPrice = Math.min($scope.minPrice, price);
										$scope.maxPrice = Math.max($scope.maxPrice, price);
									}
									// Calendar uses min only, can show min for all dates in the range
									$scope.minCalendarPrice = Math.min($scope.minCalendarPrice, price);
								}
							}

							//Keep Chart Data and Calendar Data in Sync
							//Build Calendar Data
							$scope.calendarData[departDate.YYYY_MM_DD()] = {
								departDate: departDate,
								returnDate: returnDate,
								price: price
							};

							//Build Chart Data only for the visible range
							if (i <= VISIBLE_DAYS_COUNT || $rootScope.isMobile) {
								$scope.chartData.push({ departDate: departDate, returnDate: returnDate, price: price });
							}

						}

						// Include the selected price in the calculation, for proper scaling, only for the chart
						if ($rootScope.isMobile) {
							$scope.minPrice = Math.min($scope.minPrice, $scope.initialPrice.price);
							$scope.maxPrice = Math.max($scope.maxPrice, $scope.initialPrice.price);
						} else {
							$scope.minPrice = Math.min($scope.minPrice, $scope.selectedPrice.price);
							$scope.maxPrice = Math.max($scope.maxPrice, $scope.selectedPrice.price);
						}

						// Raise the ceiling for the max price in order to keep some padding at the top of the chart.
						$scope.maxPrice += Math.round($scope.maxPrice * CEILING_FACTOR);

						calculatePriceRatios();
						window.setTimeout(function () {
							$animate.enabled(true);
						}, 0);
					}

					// Caluclate and set the price ratios for the results
					function calculatePriceRatios() {
						for (var i = 0; i < $scope.chartData.length; i++) {
							if ($scope.chartData[i].price) {
								if ($scope.chartData[i].price === $scope.minPrice && $scope.chartData[i].price === $scope.maxPrice) {
									$scope.chartData[i].ratio = 1;
								} else {
									$scope.chartData[i].ratio = $scope.chartData[i].price / $scope.maxPrice;
								}
							}
						}

						$scope.selectedPrice.ratio = Math.min($scope.selectedPrice.price / $scope.maxPrice, 1);
						if ($rootScope.isMobile) {
							$scope.initialPrice.ratio = Math.min($scope.initialPrice.price / $scope.maxPrice, 1);
						}
					}

					// Check to see if the buffer has run out
					function findBufferGap(direction) {

						// Forward click
						if (direction > 0) {
							var lastBufferDate = new Date($scope.lastBufferDate);
							var newLastVisibleDate = new Date($scope.currentDate);
							newLastVisibleDate.dateAdd('day', VISIBLE_DAYS_COUNT - 1);
							if (haDateUtils.isAfter(newLastVisibleDate, lastBufferDate)) {
								return true;
							}
						} else { // Backward click
							var firstBufferDate = new Date($scope.firstBufferDate);
							var newFirstVisibleDate = new Date($scope.currentDate);
							if (haDateUtils.isBefore(newFirstVisibleDate, firstBufferDate)) {
								return true;
							}
						}
						return false;
					}

					// Calendar forward/backward
					function calendarAction(direction) {

						if ($scope.error) {
							$scope.model.loading = false;
							return;
						}
						var dateStart;
						var tempDate;
						var fetch = true;

						if (direction > 0) {
							dateStart = new Date($scope.lastBufferDate);
							//Cycle forward 1 month.  Set the new current date to be the first of the month
							$scope.currentDate = new Date($scope.currentDate.getFullYear(), $scope.currentDate.getMonth() + 1, 1);
							tempDate = new Date($scope.currentDate.getFullYear(), $scope.currentDate.getMonth() + 2, 1);
							fetch = (haDateUtils.isAfter(tempDate, $scope.lastBufferDate)) ? true : false;
							fetch = ($scope.maxForward && $scope.maxForward.date) ? false : fetch; // if max date has been reached, overwrite to false
						} else {
							dateStart = new Date($scope.firstBufferDate.getTime());
							//Cycle backward 1 month.  Set the new current date to be the first of the month
							$scope.currentDate = new Date($scope.currentDate.getFullYear(), $scope.currentDate.getMonth() - 1, 1);
							// Are we querying for dates before the buffer AND no further in the past than today?
							fetch = haDateUtils.isBefore($scope.currentDate, $scope.firstBufferDate) && !moment(new Date()).isSame(dateStart, 'day');
						}

						// if max date has been reached, update the flag for forward arrow
						if ($scope.maxForward && $scope.maxForward.date) {
							$scope.maxForward.disableForward = (haDateUtils.isAfter(tempDate, $scope.lastBufferDate)) ? true : false;
						}

						if (fetch) {
							$scope.model.loading = true;
							priceApi.fetchPrices(moment(dateStart).add(direction, 'day').format('YYYY-MM-DD'), direction, { cache: true }).success(function (response) {

								if (response.IsSuccess) {
									addResultsToBuffer(response.LowFareTripResponseList);
									// if max date range was reached in the backend, save the last returned date to avoid trying to get any more results
									if (response.isMaxDateReached) {
										$scope.maxForward = {
											date: $scope.results[$scope.results.length - 1].DepartDate,
											disableForward: true
										};
									}
									refreshData();
								}
								$scope.model.loading = false;
							});
						} else {
							refreshData();
							$scope.model.loading = false;
						}
					}

					// Move Callback, retrieves updated data
					function moveCallback(direction) {

						// Determine what to query the API for
						var getData = findBufferGap(direction);
						$scope.moving = false;
						$scope.model.loading = true;
						if (getData) {
							var dateStart = (direction > 0) ? new Date($scope.lastBufferDate) : new Date($scope.firstBufferDate);
							priceApi.fetchPrices(moment(dateStart).format('YYYY-MM-DD'), direction, { cache: true }).success(function (response) {
								if (response.IsSuccess) {
									addResultsToBuffer(response.LowFareTripResponseList);
									refreshData();
								}
								$scope.model.loading = false;
							});
						} else {
							refreshData();
							$scope.model.loading = false;
						}
						$scope.$apply();
					}

					// Calculate how many days for each visible month should be shown
					function updateMonths() {
						$scope.preventLeft = false;
						$scope.preventRight = false;
						$scope.endOfMonthIndex = null;
						var visibleMonths = [];
						var tempDate = $scope.chartData[0].departDate;
						var month = {
							month: tempDate.getMonth(),
							year: tempDate.getFullYear(),
							days: 1
						};
						for (var i = 1; i < VISIBLE_DAYS_COUNT; i++) {
							tempDate = $scope.chartData[i].departDate;
							if (tempDate.getMonth() !== month.month) {
								visibleMonths.push(month);
								$scope.endOfMonthIndex = i - 1;
								month = {
									month: tempDate.getMonth(),
									year: tempDate.getFullYear(),
									days: 1
								};
							} else {
								month.days++;
							}

							if (tempDate <= Date.now()) {
								$scope.preventLeft = true;
							}
							if (haDateUtils.numDaysDifference(new Date(), tempDate) >= MAX_DAYS) {
								$scope.preventRight = true;
							}
						}
						visibleMonths.push(month);
						$scope.visibleMonths = visibleMonths;
					}

					// SCOPE METHODS
					//*******************
					// Update Prices
					$scope.updatePrices = function () {
						$scope.error = false;
						$scope.model.loading = true;
						var requestJSON = buildRequestJSON();
						$timeout(function () {
							priceApi.fetchInitialPrices({
								data: requestJSON,
								config: { cache: true }
							}).then(function (response) {
								if (response.IsSuccess) {
									$scope.results = response.LowFareTripResponseList;
									// if max date range was reached in the backend, save the last returned date to avoid trying to get any more results
									if (response.isMaxDateReached) {
										$scope.maxForward = {
											date: $scope.results[$scope.results.length - 1].DepartDate,
											disableForward: true
										};
									}
								} else {
									$scope.error = true;
									$scope.results = [];
								}
								$timeout(function() {
									$scope.model.loading = false;
								});
							}, 50);
						});

					};

					// Move a specified number of days
					$scope.move = function (days) {
						if (animating || $scope.error) {
							$scope.model.loading = false;
							return;
						}
						$scope.moving = true;
						animating = true;
						$scope.currentDate.setDate($scope.currentDate.getDate() + days);
						var direction = days / Math.abs(days); // 1 or -1
						window.clearTimeout(moveTimeout);
						moveTimeout = window.setTimeout(function () {
							moveCallback(direction);
						}, MOVE_TIMEOUT_MILLESECONDS);
						refreshData();
						updateMonths();
					};

					$scope.monthFilter = function (value) {
						return $scs('LowFare.' + monthKeys[value]);
					};

					$scope.getFlightResultsLink = function (depart) {
						var url = '/Book/FlightResults?l=' + $scope.legs[0].origin.code + '-' + $scope.legs[0].destination.code + '+' + moment(depart).format('YYYY-MM-DD');
						if ($scope.legs[1]) {
							url += ',' + $scope.legs[1].origin.code + '-' + $scope.legs[1].destination.code + '+' + moment(depart).add($scope.model.tripLength, 'days').format('YYYY-MM-DD');
						}
						if ($scope.adults) {
							url += '&a=' + $scope.adults;
						}
						if ($scope.children) {
							url += '&c=' + $scope.children;
						}

						return url;
					};

					// Get Lowline Style (top)
					$scope.getLowLineStyle = function () {
						return 'calc(' + ((1 - $scope.selectedPrice.ratio) * 100).toString() + '% + 45px)';
					};

					// Get Price Bar Style (top)
					$scope.getBarStyle = function (r) {
						return r.price ? ((1 - (r.ratio)) * 100).toString() + '%' : (($scope.model.loading || $scope.moving) ? '100%' : '-23px');
					};

					// Get Month Style (width/left)
					$scope.getMonthStyle = function (month, dim, idx) {
						return (dim === 'width') ? ((month.days / 25) * 100).toString() + '%' : (idx ? ((1 - (month.days / 25)) * 100).toString() : '0') + '%';
					};

					// Get Tooltip Style (top)
					$scope.getTipStyle = function (r) {
						return r.price ? (Math.min((1 - (r.ratio)) * 100, 84).toString()) + '%' : (($scope.model.loading || $scope.moving) ? '100%' : '-23px');
					};

					// Set display
					$scope.setDisplay = function (d) {
						$scope.display = d;
						initialize();
					};

					$scope.dayPlus = function (date, duration) {
						return (angular.isDate(date)) ? (new Date(+date)).dateAdd('day', duration) : new Date();
					};

					// for mobile
					$scope.selectDate = function (date, event) {
						if ($rootScope.isMobile) {
							event.preventDefault();
							if ($scope.calendarData[date.YYYY_MM_DD()]) {
								$timeout(function () {
									$scope.legs[0].departDate = date;
									$scope.selectedPrice.date = date;
									$scope.selectedPrice.price = $scope.calendarData[date.YYYY_MM_DD()].price;
								});
							}
						}
					}

					function getScrollPosition() {
						var modalNode = element.closest('.ha-modal')[0];
						return modalNode.scrollHeight - modalNode.scrollTop;
					};

					$scope.moveMobile = function (direction) {
						if ($scope.error || !$rootScope.isMobile) {
							return false;
						}
						$scope.model.loading = true;
						var dateStart = (direction > 0) ? new Date($scope.lastBufferDate) : new Date($scope.firstBufferDate);

						priceApi.fetchPrices(moment(dateStart).format('YYYY-MM-DD'), direction, { cache: true }).success(function (response) {
							if (response.IsSuccess) {
								// If we are loading previous dates, we want to store the current scroll position
								if (direction < 0) {
									var oldScrollPosition = getScrollPosition();
									$timeout(function () {
										var modalNode = element.closest('.ha-modal')[0];
										modalNode.scrollTop = modalNode.scrollHeight - oldScrollPosition;
									}, 0);
								}
								addResultsToBuffer(response.LowFareTripResponseList);
								refreshData();
								// add more months to calendar
								var event = (direction > 0) ? 'calendarGoForward' : 'calendarGoBackward';
								$scope.$broadcast(event, 3);
							}
							if (response.isMaxDateReached) {
								$scope.preventRight = true;
							}
							refreshBoundariesMobile();
							$timeout(function() {
								$scope.model.loading = false;
							});
						});
					};
					function refreshBoundariesMobile() {
						if (haDateUtils.numDaysDifference(new Date($scope.firstBufferDate), new Date(moment(Date.now()).utc().format("MM/DD/YYYY"))) <= 1) {
							$scope.preventLeft = true;
						}
						if (haDateUtils.numDaysDifference(new Date(), new Date($scope.lastBufferDate)) >= MAX_DAYS) {
							$scope.preventRight = true;
						}
					}
					// hide the load previous button under the modal-header until user scrolls up
					$scope.setScrollPos = function () {
						if ($('#load-previous-row').length) { // this will only exist on mobile
							// var offset = $('#load-previous-row').outerHeight(); // doesn't render string in time, incorrect height
							$('#FlexiblePriceView')[0].scrollTop = 82; // offset;
						}
					}
					var initChartPos = true;
					$scope.setChartPos = function () {
						if (initChartPos && $rootScope.isMobile) {
							initChartPos = false;
							$timeout(function () {
								var $selectedPriceElement = element.find('.selected-price');
								if ($selectedPriceElement.length) {
									element.closest('.ha-modal').scrollTop($selectedPriceElement.offset().top - ($(window).height() / 2));
								}
							}, 0);
						}
					}

					// INITIALIZATION
					//*******************
					function initialize() {

						// Initializations
						if (typeof $scope.legs === 'string') {
							$scope.legs = haUtils.parseLegs($scope.legs);
							//adding read DisplayNames will require an API call- so for now just use the Airport codes
							$scope.legs[0].origin.display = $scope.legs[0].origin.code;
							$scope.legs[0].destination.display = $scope.legs[0].destination.code;
						}

						$scope.minPrice = Number.POSITIVE_INFINITY;
						$scope.maxPrice = Number.NEGATIVE_INFINITY;
						$scope.buffer = {};    // The days we've loaded so far
						$scope.visibleMonths = [];
						$scope.display = $scope.display || 'calendar';
						$scope.maxForward = {}; // for keeping track of the last max date backend returned

						//Since the modal can be opened and closed, and the views share scope hard reset values when it's opened and when the view changes
						$scope.chartData = []; //set or reset
						$scope.calendarData = {}; //set or reset
						$scope.currentDate = undefined; //set or reset
						$scope.firstBufferDate = undefined; //set or reset
						$scope.lastBufferDate = undefined; //set or reset
						$scope.visibleMonths = []; //set or reset

						// Trip Length
						$scope.model = {
							tripLength: $scope.legs.length === 2 ? moment($scope.legs[1].departDate).diff($scope.legs[0].departDate, 'days') : 0,
							loading: false
						};
						//Don't bother detecting multi-destination.
						//This modal is only applicable for 1 way or round trips.
						//If someone sends in invalid data- we'll have problems everywhere.
						$scope.tripType = $scope.legs.length;

						// Build day array
						$scs.fetch('LowFare.nighttext,LowFare.nightstext').then(function (values) {
							$scope.nightValues = angular.copy(Array(61)).map(function (x, i) {
								return i + ' ' + values[1];
							});
							$scope.nightValues[0] = '0 ' + values[0];
							$scope.nightValues[1] = '1 ' + values[0];
							$scope.$apply();
						});

						resetDates();
						$scope.updatePrices();
					}

					initialize();
				}
			};
		}
	]);

	// Animations
	module.animation('.month-animation', function () {
		return {
			enter: function (element, done) {
				element.css({ 'opacity': 0 });
				element.delay(500).animate({ 'opacity': 1 }, {
					complete: done
				});
			}
		};
	});
	module.animation('.price-item-animation', function () {
		return {
			enter: function (element, done) {
				if (element.hasClass('first-item')) {
					// The very first element
					var oldMargin = element.css('margin-left');
					element.css({
						'margin-left': 0
					});
					element.animate({
						'margin-left': oldMargin
					}, {
						easing: 'linear',
						duration: 500,
						complete: done
					});
					return function () {
						element.css({
							'margin-left': ''
						});
						animating = false;
					};
				} else {
					element.animate({
						'background-color': 'transparent'
					}, {
						duration: 500,
						complete: done
					});
				}
			},
			leave: function (element, done) {
				element.animate({
					'margin-left': 0
				}, {
					easing: 'linear',
					duration: 500,
					complete: done
				});

				if (element.hasClass('first-item')) {
					var newFirst = element.nextAll(':not(.ng-leave-prepare)').first();
					newFirst.css({
						'margin-left': 0
					});
					return function () {
						newFirst.css({
							'margin-left': ''
						});
						animating = false;
					};
				}
			}
		};
	});

})(angular);
;
(function (angular) {
	'use strict';

	var mod = angular.module('haKisaTermsModule', []);

	mod.directive('haKisaTermsCheckboxLaunch', ['$window', '$rootScope', 'haConfig', 'haModal', function ($window, $rootScope, haConfig, haModal) {

		return {
			restrict: 'A',
			scope: false,
			link: function ($scope, element, attrs) {
				var termsName = attrs.contentVar || 'termsAndConditions';

				// If a checkbox ngModel is present, hook it up to modal success/failure events
				if (attrs.ngModel) {
					$rootScope.$on('termsModalFailure', function(e, terms) {
						if (terms === termsName) {
							$scope.$eval(attrs.ngModel + ' = false');
						}
					});
					$rootScope.$on('termsModalSuccess', function(e, terms) {
						if (terms === termsName) {
							$scope.$eval(attrs.ngModel + ' = true');
						}
					});
				}

				// Generate and append label markup
				if ($window[termsName] && $window[termsName].ParentCheckBoxText) {
					element.after('<label for="' + attrs.id + '" class="required">' +
									$window[termsName].ParentCheckBoxText +
								   '</label>');
				}

				// Event handler to open modal
				$scope.termsStart = function() {
					if (!$window[termsName]) {
						return;
					}
					$rootScope.$broadcast('termsModalStart', termsName);

					haModal(haConfig.getTemplateUrl('ha-kisa-terms-modal.html'), {
						id: 'kisaTermsModal',
						backdrop: true,
						modalLock: true,
						scope: $scope,
						extendScope: {
							scContent: $window[termsName],
							termsName: termsName
						}
					});
				};
			}
		};
	}])

	mod.directive('haKisaTermsModal', ['$rootScope', 'haConfig', 'haModal', function ($rootScope, haConfig, haModal) {

		return {
			restrict: 'A',
			scope: false,
			link: function ($scope) {

				$rootScope[$scope.termsName] = $rootScope[$scope.termsName] || {
					allAccepted: false,
					themes: $scope.scContent.PrivacyPolicies
				}
				$scope.terms = $rootScope[$scope.termsName];

				$scope.termsToggleAll = function() {
					var changeto;

					if ( !$scope.terms.allAccepted ) {
						changeto = false;
					} else {
						changeto = true;
					}

					$scope.terms.themes.forEach( function(v, i) {
						v.accepted = changeto;
					});
				};

				$scope.termsToggleTheme = function(i) {
					if (!$scope.terms.themes[i].accepted) {
						$scope.terms.allAccepted = false;
					}
				};

				$scope.cancelTerms = function() {
					$rootScope.$broadcast('termsModalFailure', $scope.termsName);
					$scope.$modalCancel();
				};

				$scope.submitTerms = function() {
					$rootScope.$broadcast('termsModalSuccess', $scope.termsName);
					$scope.$modalSuccess();
				};
			}
		};
	}]);

})(angular);
;
(function (angular) {
	'use strict';

	var mod = angular.module('haIslandGuideMapModule', []);

	mod.directive('haIslandGuideMap', ['$rootScope', function ($rootScope) {

		return {
			restrict: 'A',
			scope: true,
			link: function ($scope, element) {

				// find clibckable islands and labels
                var islandLabels = $('#islandGuideMap .islandLabel');
                var islandArts = $('#islandGuideMap .islandArt');
                var linkedItems = islandLabels.add(islandArts);

                var cancelledAt = 1.05;

                if ( !$rootScope.isMobile ) {

	                islandArts.hover( function(e) {

						var animationDiv= $("<div></div>"); //we don't add this div to the DOM
						var svg = $(e.target).closest(".islandArt").find('*');

						animationDiv.css("left", 1.00);
						animationDiv.animate(
							{
								left: 1.05,
							},
							{
								duration: 500,
								step: function(value) {
									cancelledAt = value;
									svg.each(function(i,el) {
										var box = el.getBBox();
										var cx = box.width/2;
										var cy = box.height/2;
										var x = box.x;
										var y = box.y;
										var transX = -(cx+x)*(value-1);
										var transY = -(cy+y)*(value-1);

										$(el).attr('transform', 'translate('+ transX + ',' + transY +')' + ' scale(' + value + ')');
									});
								}
							}
						);
					}, function(e) {
						var animationDiv= $("<div></div>");
						var svg = $(e.target).closest(".islandArt").find('*');

						animationDiv.css("left", cancelledAt || 1.05);
						animationDiv.animate(
							{
								left: 1.00,
							},
							{
								duration: 500,
								step: function(value, properties) {
									svg.each(function(i,el) {
										var box = el.getBBox();
										var cx = box.width/2;
										var cy = box.height/2;
										var x = box.x;
										var y = box.y;
										var transX = -(cx+x)*(value-1);
										var transY = -(cy+y)*(value-1);

										$(el).attr('transform', 'translate('+ transX + ',' + transY +')' + ' scale(' + value + ')');
									});
								}
							}
						);

	                });

                }

                linkedItems.click( function(e) {
                    window.location = $(this).attr('data-href');
                });

			}
		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	var module = angular.module('haNativeAppModalModule', []);

	module.directive('haNativeAppModal', [
		'$location',
		'$rootScope',
		'haGlobals',
		'haUtils',

		function ($location, $rootScope, haGlobals, haUtils) {

			var link = function ($scope) {
				//>>> Native App Modal
				// daysToHide is global from Sitecore to _PartialModalNativeappLinkSms.cshtml
				haGlobals('daysToHide', function (val) {

					if (!val) { // if undefined
						$scope.daysToHide = 0;
						return;
					}

					if (val < 0) {
						$scope.daysToHide = 0;
						return;
					}

					$scope.daysToHide = val;
				});

				$scope.createModalHideExpirationCookie = function (created_date_string) {
					var currentDate = new Date();
					if (!!created_date_string) {
						currentDate = new Date(created_date_string);
					}

					var expiredDate = new Date();
					expiredDate.setTime(currentDate.getTime() + ($scope.daysToHide.toString() * 24 * 60 * 60 * 1000));

					var expires = "expires=" + expiredDate.toUTCString();

					if (HA.CookiesRequireSsl) {
						document.cookie = ['ModalHideExpirationCookie=' + currentDate.toUTCString() + '; ', expires, '; secure; ', '; path=/'].join('');
					} else {
						document.cookie = ['ModalHideExpirationCookie=' + currentDate.toUTCString() + '; ', expires, '; path=/'].join('');
					}
				};

				$scope.getModalHideExpirationCookie = function () {

					var theValue = '';

					var checkCookie = haUtils.readCookie('ModalHideExpirationCookie');
					if (!!checkCookie) {
						theValue = decodeURIComponent(checkCookie);
					}

					return theValue;
				};

				$scope.removeModalHideExpirationCookie = function () {
					document.cookie = encodeURIComponent('ModalHideExpirationCookie') + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + "; domain=; path=";
				};

				$(window).load(function () {

					// check if USA
					haGlobals('defaultCountryCode', function (code) {
						if (code !== "USA") {
							return;
						}
					});

					var cookieVal = '';
					cookieVal = $scope.getModalHideExpirationCookie();
					if (cookieVal !== '' && cookieVal !== "Invalid Date") {
						// cookie found. check the date of creation.
						var created_date_string = cookieVal;
						var current_date_string = new Date().toUTCString();

						// Update the cookie as daysToHide might have been changed.
						$scope.createModalHideExpirationCookie(created_date_string);

						// Check the Sitecore value and the day passed.
						var created_date = moment(created_date_string).isValid() ? moment(created_date_string) : moment();
						var current_date = moment(current_date_string).isValid() ? moment(current_date_string) : moment();
						if ($scope.daysToHide > current_date.diff(created_date, 'days')) {
							// no need to show modal
							return;

						} else {
							// show modal
							haModal({
								id: 'modal-nativeapp-link-sms',
								backdrop: 'true',
								template: angular.element('#modal-nativeapp-link-sms'),
								scope: $scope
							});
						}
					} else { // cookie not found
						// show modal
						haModal({
							id: 'modal-nativeapp-link-sms',
							backdrop: 'true',
							template: angular.element('#modal-nativeapp-link-sms'),
							scope: $scope,
							cancel: {
								label: 'Close',
								fn: $scope.createModalHideExpirationCookie
							}
						});
					}
				});
				//<< Native App Modal
			};

			return {
				restrict: 'A',
				scope: true,
				link: link
			};
		}]
	);
})(angular);
;
(function (angular) {
	'use strict';
	var PER_PAGE = 3;
	var module = angular.module('haDealTilesModule', []);

	module.directive('haDealTiles', [function () {

		var haDealTilesLink = function($scope, $el, $attrs) {
			var viewModel = angular.fromJson($attrs.dealsData);
			$scope.deals = viewModel.DealTiles;
			console.log(viewModel);
			var dealGroups = [];
			var groupIndex = -1;

			for (var i = 0; i < $scope.deals.length; i++) {
				var deal = $scope.deals[i];
				if (i % PER_PAGE === 0) {
					dealGroups.push({ group: [] });
					groupIndex++;
				}
				dealGroups[groupIndex].group.push(deal);
			}
			$scope.dealGroups = dealGroups;

		}
		return {
			restrict: 'A',
			scope: true,
			link: haDealTilesLink,
		};
	}]);
	// TripType Enum From C# Viewmodel
	// OneWay = 0,
	// RoundTrip =1,
	// Package =2,
	// GenericRichText =3,
	// CustomFareSale =4,
	// Standard =5,
	// None
	module.directive('haDealTile', ['haConfig', '$rootScope', function (haConfig, $rootScope) {
		var templateUrl = "ha-deal-tile-base-template.html";
		var haDealTileLink = function($scope, $el, $attrs) {
			console.log('deal tile 2020 directive link');
			// $scope.model = $attrs['model']
			console.log($scope.model);

			$scope.model.LowestFare = Math.min($scope.model.MainCabinBasicFare || Number.POSITIVE_INFINITY, $scope.model.Fare);

			// Ref Marks/Disclaimers
			$scope.referenceMark = $scope.referenceMark || '*';
			if (!$rootScope.footnotes || !$rootScope.footnotes.numeric || !$rootScope.references) {
				$rootScope.footnotes = { numeric: [] };
				$rootScope.references = {};
			}

			if (!$rootScope.references[$scope.model.DisclaimerGuid]) {

				if ($scope.model.Disclaimer && $scope.model.TripType != 3) {
					$rootScope.references[$scope.model.DisclaimerGuid] = $scope.referenceMark + (Object.keys($rootScope.references).length + 1);
					$scope.model.DisclaimerReferenceMark = $rootScope.references[$scope.model.DisclaimerGuid];

					var isMCBFare = $scope.model.LowestFare && $scope.model.LowestFare === $scope.model.MainCabinBasicFare;
					var lowFareDisclaimer = isMCBFare ? $scope.model.MainCabinBasicDisclaimer : $scope.model.Disclaimer;
					$rootScope.footnotes.numeric.push({
						id: '*' + ($rootScope.footnotes.numeric.length + 1),
						text: lowFareDisclaimer
					});
				}

			} else {
				$scope.model.DisclaimerReferenceMark = $rootScope.references[$scope.model.DisclaimerGuid];
			}
		}
		if (window.location.pathname == "/") {
			templateUrl = "ha-deal-tile-base-template-lazyload.html";
		}
		return {
			restrict: 'A',
			scope: {
				'model': '='
			},
			link: haDealTileLink,
			templateUrl: haConfig.getTemplateUrl(templateUrl)
		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	var module = angular.module('bookingWidgetSlimModule', []);

	module.directive('bookingWidgetSlim', ['$rootScope', function ($rootScope) {
		var PEEK_OFFSET = $rootScope.isMobile ? 0 : 40; // How much space above the widget should be shown upon expansion.
		var SCROLL_THRESHOLD = 2; // How far a user can scroll in the expanded state while
		                          // still saving the old scroll position.
		var bookingWidgetSlimLink = function ($scope, $element, $attrs) {

			var overlayEL = angular.element('<div id="booking-widget-overlay"></div>');

			$scope.$watch('tab', function (nv, ov) {
				if ((ov != nv && nv != 'bookflights')) {
					$scope.expanded = true;
				}
			});

			var oldScroll = null;
			$scope.$watch('expanded', function(nv, ov) {
				if ($rootScope.isMobile) { return; }
				if (nv) {
					$element.before(overlayEL);
					overlayEL.click(function(e) {
						$scope.expanded = false;
						$scope.$apply();
					});
					oldScroll = window.pageYOffset;
					$('html, body').animate({
						scrollTop: $element.offset().top - PEEK_OFFSET
					});
					// window.scrollTo({left: 0, top: $element.offset().top - PEEK_OFFSET, behavior: 'smooth'});
				} else {
					overlayEL.remove();
					var expandedHeight = $element.outerHeight();
					if (oldScroll != null && Math.abs(window.pageYOffset - ($element.offset().top - PEEK_OFFSET)) <= SCROLL_THRESHOLD) {
						window.scrollTo({left: 0, top: oldScroll});
					}
					oldScroll = null;
					$scope.tab = 'bookflights';
				}
			});

			$element.bind("keydown keypress", function (event) {
                if(event.which === 27) {
                  $scope.expanded = false;
                  $scope.$apply();
                  event.preventDefault();
                }
            });

		};

		return {
			restrict: 'A',
			scope: true,
			link: bookingWidgetSlimLink
		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	var module = angular.module('haSitecoreModule', ['haGlobalsModule']);
	var failKeys = [];

	function isFailKey(key) {
		if (failKeys.indexOf(key) !== -1) {
			failKeys.splice(failKeys.indexOf(key), 1);
			return true;
		}
		return false;
	}

	function set(elm, attrs, value, name) {
		var localized = STRINGS(value);

		if (/text|html|value/.test(name)) {
			elm[name === 'value' ? 'val' : name](localized);
		}
		else {
			attrs[attrs.$normalize(name)] = localized;
			elm.attr(name, localized);
		}
		if (attrs['scsHideEmpty'] !== undefined && isFailKey(value)) {
			elm.addClass('hidden');
		}
	}

	function hyphenate(name) {
		return name.replace(/[A-Z]/g, function (c, i) {
			return (i ? '-' : '') + c.toLowerCase();
		}).replace(/^[^-]+-/, '');
	}

	function format(str, values, scope) {
		//first pass: replace all number placeholders
		str = str.replace(/{(\d+)}/g, function (match, i) {
			return values && values[i];
		});

		//second pass: $eval all other placeholders
		if (scope) {
			str = str.replace(/{([^}]+)}/g, function (match, key) {
				return (scope.$eval(key)) || match;
			});
		}
		return str;
	}

	function getKeys(elm) {
		if (!elm[0].attributes[0]) {
			throw new Error('`scs` element must have a keys attribute');
		}
		return elm[0].attributes[0].name;
	}

	function fail(key) {
		console.error('SCString not found: ', key);
		failKeys.push(key);
		return '[' + key + ']';
	}

	var STRINGS = fail;


	module.factory('haSitecoreStrings', [
		'$parse',
		'haGlobals',
		'$q',
		'haConfig',
		'haHttpService',

		function ($parse, haGlobals, $q, config, $http) {

			var cdn = config.getSitecoreResourceUrl;
			STRINGS = function (path, params) {
				//				console.log('scs', path);
				var value = $parse(path.toLowerCase())(STRINGS);
				if (value && angular.isArray(params)) {
					value = format(value, params);
				}
				return value || fail(path);
			};
			function truthy(path) {
				var value = $parse(path.toLowerCase())(STRINGS);
				if (typeof value === 'undefined') {
					fail(path);
				}
				return !!value && !/0|false/.test(value) && value;
			}

			function request(guid) {
				return $http.GET(cdn(guid)).then(function (res) {
					return res.data ? (STRINGS[guid] = res.data) : Promise.reject(new Error('Invalid response data'));
				});
			}

			function get(ns_key) {
				var match = /^(\w+)(?:(?:\.|\[['"])([\w ]+))?/.exec(ns_key);
				if (!match) {
					return Promise.reject(new Error('Invalid ns_key argument: ' + ns_key));
				}
				var ns = match[1].toLowerCase();
				var key = match[2];
				var promise = STRINGS[ns] ? Promise.resolve(STRINGS[ns]) : (STRINGS[ns] = request(ns));
				return promise.then(function (resources) {
					return key ? STRINGS(ns_key) : resources;
				});
			}

			STRINGS.truthy = truthy;
			STRINGS.request = request;
			STRINGS.get = get;
			STRINGS.fetch = function (guids) {
				if (typeof guids !== 'string') {
					return Promise.resolve(STRINGS);
				}
				return Promise.all(guids.split(',').map(get));
			};

			haGlobals('HA', function (HA) {
				angular.extend(STRINGS, HA.SCStrings);
			});

			return STRINGS;
		}
	]);

	module.factory('haSitecore', [
		'haGlobals',
		'haConfig',
		'haHttpService',
		function (haGlobals, config, $http) {

			//$img
			var loaded = 0;
			var cdn = config.getImgUrl;
			var imageDataUrl = config.getDynamicJsonUrl('sitecoreimages');

			function img(path, attrs) {
				if (!attrs) {
					attrs = {};
				}
				path = path.toLowerCase();
				attrs.src = cdn('/static/images/' + path);
				var $img = angular.element('<img alt>').attr(attrs);
				img.alt(path).then(function (alt) {
					$img.attr('alt', alt || '');
				});
				['catch'](function (err) {
					console.error(err);
				});
				return $img;
			}

			img.alt = function (path) {
				path = path.toLowerCase();
				if (loaded) {
					var alt = img[path];
					if (typeof alt === 'undefined') {
						return Promise.reject(new Error('Sitecore image not found: ' + path));
					}
					return Promise.resolve(alt);
				}
				return $http.GET(imageDataUrl).then(function (res) {
					if (res.data) {
						loaded++;
						angular.extend(img, res.data);
						return;
					}
					if (loaded < 3) {
						return img.alt(path);
					}
					return Promise.reject(new Error('Data unavailable'));
				});
			};

			//$switch
			function sw(key) {
				return !!sw[key.toLowerCase()];
			}

			haGlobals('HA', function (HA) {
				angular.extend(sw, HA.SCSwitches);
			});

			return {
				$switch: sw,
				$img: img
			};
		}
	]);

	['scsText', 'scsHtml', 'scsLabel', 'scsEyebrow', 'scsPlaceholder', 'scsErrorMessage', 'scsHref', 'scsSrc', 'scsDescription', 'scsHeader', 'scsValue']
	.forEach(function (directiveName) {
		var destination = hyphenate(directiveName);

		module.directive(directiveName, function () {
			return {
				priority: 500,
				link: {
					pre: function (scope, elm, attrs) {
						set(elm, attrs, attrs[directiveName], destination);
					}
				}
			};
		});
	});

	module.directive('i18nContent', function () {
		return {
			restrict: 'A',
			link: function (scope, elm, attrs) {
				var keys = format(attrs.i18nContent, [], scope);
				// angular trims space from the `attrs` values so
				// we need to 'do the icky'® and get it directly off the elm.
				var formatString = elm.attr('format') || elm.text();

				STRINGS.fetch(keys).then(function (results) {
					var children = elm.contents();
					if (children.length > 1 || (children.length === 1 && children[0].nodeType !== 3 && children[0].nodeType !== 8)) {
						return console.error('i18n-content directive can only only be applied to an element with a text node child.');
					}
					var setter = elm[0].nodeName === 'INPUT' ? 'val' : 'html';
					elm[setter](formatString ? format(formatString, results, scope) : results.join(' '));
				});
			}
		};
	});

	module.directive('i18nBackgroundImage', function () {
		return {
			restrict: 'A',
			link: function (scope, elm, attrs) {
				var key = format(attrs.i18nBackgroundImage, [], scope);
				STRINGS.get(key).then(function (value) {
					elm.css('background-image', format('url(\'{0}\')', [scope.getMediaImage(value)]));
				});
			}
		};
	});

	module.directive('i18nSrc', function () {
		return {
			restrict: 'A',
			link: function (scope, elm, attrs) {
				var key = format(attrs.i18nSrc, [], scope);
				STRINGS.get(key).then(function (value) {
					elm.attr('src', scope.getMediaImage(value));
				});
			}
		};
	});

	// CS: This directive takes an input in the following format <i18nimage key="guidNameConstant.fieldName"></i18nimage>, and generates a proper image tag from the sitecore ajax endpoint
	module.directive('i18nimage', ['haUtils', function (haUtils) {
		return {
			restrict: 'E',
			replace: true,
			scope: {},
			template: '<img ng-src="{{object.src}}" alt=""/>',
			link: function ($scope, elm) {
				var key = elm.attr('key');
				STRINGS.get(key).then(function (value) {
					if (value !== '['+key+']') {
						$scope.object = haUtils.getImageObjectFromSiteCoreString(value);
						if ($scope.object.alt) {
							elm.attr('alt', $scope.object.alt);
						}
						if ($scope.object.width) {
							elm.attr('width', $scope.object.width);
						}
						if ($scope.object.height) {
							elm.attr('height', $scope.object.height);
						}
					} else {
						// display key.value text in place of the image
						elm.replaceWith(value);
					}
				});
			}
		};
	}]);

	// Parses sitecore links into anchor tags
	// Ex: <link text="All Restriction" anchor="" linktype="internal" class="" title="All Restriction" querystring="" 
	//      id="{27942FFA-4159-4C6D-B8C7-992A668EDBBA}" url="/Contact Us" />
	module.directive('i18nLink', ['$window', function ($window) {
		return {
			restrict: 'E',
			replace: true,
			scope: {},
			template: '<a></a>',
			link: function ($scope, elm) {
				var key = elm.attr('key');
				STRINGS.get(key).then(function (value) {
					var linkEl = $(value);
					if (!linkEl[0]) {
						return;
					}

					elm.attr('href', 'http://' + (linkEl.attr('linktype') === 'internal' ? $window.location.host : '') + linkEl.attr('url'));
					elm.attr('title', linkEl.attr('title'));
					elm.text(linkEl.attr('text'));
				});
			}
		};
	}]);

	module.directive('i18n', function () {
		return {
			restrict: 'E',
			link: function (scope, elm) {
				var keys = elm.attr('key') || elm.attr('keys') || getKeys(elm);
				STRINGS.fetch(keys).then(function (results) {
					var formatString = elm.text();
					elm.replaceWith(formatString ? format(formatString, results, scope) : results.join(' '));
				});
			}
		};
	});

	module.directive('scsAttrs', ['$parse', function ($parse) {
		return {
			link: {
				pre: function (scope, elm, attrs) {
					//convert attribute string '{'a':'Hello'}' to an object {a:'Hello'}
					var scsAttrs = $parse(attrs.scsAttrs)(scope);
					if (typeof scsAttrs !== 'object') {
						return;
					}
					angular.forEach(scsAttrs, set.bind(this, elm, attrs));
				}
			}
		};
	}]);


	module.run([
		'$rootScope',
		'haSitecoreStrings',
		'haSitecore',

		function ($rootScope, $scs, sitecore) {
			//assign it to __proto__ so it is accessible by isolated scopes
			$rootScope.constructor.prototype.$scs = $scs;
			$rootScope.constructor.prototype.$switch = sitecore.$switch;
			$rootScope.constructor.prototype.$img = sitecore.$img;

			//TODO: remove. These are only temp
			window.$scs = $scs;
			window.$switch = sitecore.$switch;
			window.$img = sitecore.$img;
		}
	]);

})(angular);
;
(function (angular) {

	// haAdvanceToNextInputService
	// --------------------------------------------
	//
	// * **Class:** HaAdvanceToNextInputService
	// * **Author:** Nathan Probst
	//
	// Service for advancing to the next input on valid selection.

	'use strict';

	var mod = angular.module('haAdvanceToNextInputService', []);

	mod.service('haAdvanceToNextInput', [function () {
		return {
			next: function ($element) {
				var selectable = $('input[ha-advance-to-next-input]');
				var currentIndex = selectable.index($element.find('input[ha-advance-to-next-input]'));
				var nextElement = selectable.eq(currentIndex + 1);

				if (nextElement.length > 0 && currentIndex >= 0) {
					setTimeout(function () {
						nextElement.focus();
					}, 0);
				}
			}
		};
	}]);

	// Ensure the service is initialized
	mod.run(['$rootScope', 'haAdvanceToNextInput', function ($rootScope, svc) {
		$rootScope.$on('haAdvanceToNextInput:next', function (event, $element) {
			svc.next($element);
		});
	}]);

})(angular);
;
(function (angular) {

	// Ha Help And Tips Service
	// --------------------------------------------
	//
	// * **Class:** HaCalendarEventsService
	// * **Author:** George Pantazis
	//
	// Service for asynchronously retrieving different types of events for the calendar,
	// or other date-specific components.

	'use strict';

	var mod = angular.module('haCalendarEventsService', []);

	mod.service('haCalendarEventsService', function () {

		// Mock model. Replace this with an $http call to retrieve API data
		// during integration with the HA API infrastructure.

		var model = {
			// A random ID.
			'123': {
				// Returns an array of days under the type `promoDays`.
				'promoDays': [{
					'start': {
						'year': 2013,
						'month': 10,
						'day': 20
					},
					'end': {
						'year': 2013,
						'month': 11,
						'day': 1
					}
				}, {
					'start': {
						'year': 2013,
						'month': 11,
						'day': 16
					},
					'end': {
						'year': 2013,
						'month': 11,
						'day': 18
					}
				}, {
					'start': {
						'year': 2013,
						'month': 11,
						'day': 30
					},
					'end': {
						'year': 2013,
						'month': 12,
						'day': 1
					}
				}]
			},

			'456': {
				'unavailableDays': [{
					'start': {
						'year': 2013,
						'month': 11,
						'day': 8
					},
					'end': {
						'year': 2013,
						'month': 11,
						'day': 10
					}
				}]
			},

			// Can return multiple kinds of days.
			'789': {
				'unavailableDays': [{
					'start': {
						'year': 2013,
						'month': 11,
						'day': 8
					},
					'end': {
						'year': 2013,
						'month': 11,
						'day': 10
					}
				}],
				'promoDays': [{
					'start': {
						'year': 2013,
						'month': 10,
						'day': 20
					},
					'end': {
						'year': 2013,
						'month': 11,
						'day': 1
					}
				}, {
					'start': {
						'year': 2013,
						'month': 11,
						'day': 16
					},
					'end': {
						'year': 2013,
						'month': 11,
						'day': 18
					}
				}, {
					'start': {
						'year': 2013,
						'month': 11,
						'day': 30
					},
					'end': {
						'year': 2013,
						'month': 12,
						'day': 1
					}
				}]
			}
		};

		var service = {

			// $http request would occur here; dependencies on this service should utilize
			// the `cb` to pass a function, and expect results asynchronously.

			// Using a timeout to simulate async response.

			getEvents: function (id, cb) {
				setTimeout(function () {
					cb(model[id]);
				}, 1);
			}

		};

		return service;
	});

})(angular);
;
(function (angular) {

	// Hawaiian Cities Service
	// =========================
	//
	// * **Class:** haCities
	// * **Author:** Nathan Probst
	//
	// A service for returning data about cities HA services

	'use strict';

	function repromise(fn, times) {
		if (typeof times === 'undefined') {
			times = 3; // default
		}
		return fn().then(
			undefined, // pass through success
			function (err) {
				// only on timeout or specific error codes
				if ((times > 0) && ([0, 408, 500, 502, 503, 504].indexOf(err.status) >= 0)) {
					return repromise(fn, times - 1); // retry
				}
				throw err; // rethrow
			}
		);
	}

	var module = angular.module('haCitiesModule', ['haCitiesAPI']);

	module.factory('haCitiesSvc', [
		'$q',
		'$log',
		'$filter',
		'haCitiesAPI',

		function ($q, $log, $filter, api) {

			var svc = {};
			var isExpertBooking = false;

			var filter = function (list, fn) {
				if (fn != null) {
					// Allow named, registered filter functions...
					if (typeof fn === 'string') {
						fn = $filter(fn);
					}
					if (typeof fn !== 'function') {
						return list;
					}
					var fList = [];
					for (var i = 0; i < list.length; i++) {
						if (list[i] != null && fn(list[i])) {
							fList.push(list[i]);
						}
					}
					return fList;
				}
				return list;
			};

			var getCityListPromiseExpertBooking = function (filterFn) {
				isExpertBooking = true;
				return getCityListPromise(filterFn);
			};

			var getCityListPromise = function (filterFn) {
				if (isExpertBooking && !svc.CITY_LIST_EXPERT_BOOKING) {
					svc.CITY_LIST = null; // bust any old cities if we're in ExpertBooking
				}
				if (svc.CITY_LIST) {
					var deferred = $q.defer();
					deferred.resolve(filter(svc.CITY_LIST, filterFn));

					return deferred.promise;
				} else {
					var cityListFn = isExpertBooking ? api.getCityListExpertBooking : api.getCityList;
					return repromise(cityListFn).then(function (data) {
						svc.CITY_LIST = data;
						if (isExpertBooking) {
							svc.CITY_LIST_EXPERT_BOOKING = data;
						}
						return filter(svc.CITY_LIST, filterFn);
					});
				}
			};

			var getFilteredCityListPromise = function (legIndex, isOrigin) {
				// create a re-callable closure
				var fn = function () {
					return api.getFilteredCityList(legIndex, isOrigin);
				};
				return repromise(fn).then(function (cityList) {
					return cityList;
				});
			};


			var getCityMapPromise = function () {
				if (svc.CITY_MAP != null) {
					var deferred = $q.defer();
					deferred.resolve(svc.CITY_MAP);

					return deferred.promise;
				} else {
					return getCityListPromise().then(function (cities) {
						svc.CITY_MAP = {};
						for (var i = 0; i < cities.length; i++) {
							svc.CITY_MAP[cities[i].Code] = cities[i];
						}

						return svc.CITY_MAP;
					});
				}
			};

			var getCityPairsPromise = function () {
				if (isExpertBooking && !svc.CITY_PAIRS_EXPERT_BOOKING) {
					svc.CITY_PAIRS = null; // bust any old pairs if we're in ExpertBooking
				}
				if (svc.CITY_PAIRS) {
					var deferred = $q.defer();
					deferred.resolve(svc.CITY_PAIRS);

					return deferred.promise;
				} else {
					var cityPairsFn = isExpertBooking ? api.getCityPairsExpertBooking : api.getCityPairs;
					return repromise(cityPairsFn).then(function (data) {
						svc.CITY_PAIRS = data;
						if (isExpertBooking) {
							svc.CITY_PAIRS_EXPERT_BOOKING = data;
						}
						return svc.CITY_PAIRS;
					});
				}
			};

			var getNitpMapPromise = function () {
				if (svc.NITP_MAP != null) {
					var deferred = $q.defer();
					deferred.resolve(svc.NITP_MAP);

					return deferred.promise;
				} else {
					return repromise(api.getNitpMap).then(function (data) {
						svc.NITP_MAP = data;

						return svc.NITP_MAP;
					});
				}
			};

			var getCityByCodePromise = function (code) {
				return getCityMapPromise().then(function (map) {
					return map[code];
				});
			};

			var getCityByCodeSync = function (code) {
				return svc.CITY_MAP[code];
			};

			// returns an array of codes that are valid pairs with origin
			var getValidPairs = function (originCode, maxHops) {
				var result = [];
				maxHops = maxHops || 1;
				if (!svc.CITY_PAIRS || !svc.CITY_MAP) {
					return [];
				}
				var originPairs = svc.CITY_PAIRS[originCode]
				for (var p = 0; p < originPairs.length; p++) {
						result.push(svc.CITY_MAP[originPairs[p]])
				}
				return result;
			};

			var getValidPairsPromise = function (originCode, maxHops) {

				maxHops = maxHops || 1;

				return $q.all([getCityPairsPromise(), getCityMapPromise()]).then(function (resolved) {
					return getValidPairs(originCode, maxHops);
				});
			};

			var getReachableCitiesPromise = function (originCode, maxHops, filterFn) {
				// All cities are considered reachable from undefined origin.
				if (originCode == null) {
					return getCityListPromise(filterFn);
				}

				maxHops = maxHops || 1;

				// var then;
				// if (typeof performance !== 'undefined') {
				//     then = performance.now();
				// }

				return getValidPairsPromise(originCode, maxHops).then(function (result) {
					return filter(result, filterFn);
				});
			};

			// Make token pairs to handle searches like "San Fr"
			var makeTokenPairs = function (tokens) {
				var pairs = [];

				for (var i = 0; i < tokens.length - 1; i++) {
					pairs.push(tokens[i] + ' ' + tokens[i + 1]);  // Pair with space
					pairs.push(tokens[i] + tokens[i + 1]);        // Pair without space
				}

				return tokens.concat(pairs);
			};

			var weightedMatch = function (city, nameOrCode) {
				var sum = 0;

				if (nameOrCode == null) {
					return sum;
				}

				// Perfect match
				if (city.DisplayName === nameOrCode) {
					sum = 100;
					// $log.debug('Perfect', sum, city.DisplayName);
				}

				var concatValue = nameOrCode.split(' ').join('');

				if ((city.Code != null) && (city.Code.toUpperCase() === nameOrCode.toUpperCase())) {
					sum += 1;
					// $log.debug('Code', sum, city.DisplayName);
				}

				if (city.DisplayName != null) {
					var displayNameTokens = makeTokenPairs(city.DisplayName.split(' '));

					angular.forEach(displayNameTokens, function (token) {
						if (token.toLowerCase().indexOf(nameOrCode.toLowerCase()) === 0) {
							sum += 0.3;
							// $log.debug('Token', sum, city.DisplayName);
						} else if (token.toLowerCase().indexOf(concatValue.toLowerCase()) === 0) {
							sum += 0.15;
							// $log.debug('Token', sum, city.DisplayName);
						}
					});
				}

				if (city.LinkedAirportCodes != null) {
					var linkedAirportCodes = city.LinkedAirportCodes.split(',');

					angular.forEach(linkedAirportCodes, function (code) {
						if (code.toUpperCase() === nameOrCode.toUpperCase()) {
							sum += 0.2;
							// $log.debug('Linked', sum, city.DisplayName);
						}
					});
				}

				if (city.SearchTags != null) {
					var searchTagTokens = makeTokenPairs(city.SearchTags.split(' '));

					angular.forEach(searchTagTokens, function (token) {
						if ((token.toLowerCase().indexOf(nameOrCode.toLowerCase()) === 0) || (token.toLowerCase().indexOf(concatValue.toLowerCase()) === 0)) {
							sum += 0.1;
							// $log.debug('Search', sum, city.DisplayName);
						}
					});
				}

				// Only boost weight for HA Cities that aleady match!
				if ((sum > 0) && (city.IsHACity)) {
					sum += 0.5;
					// $log.debug('HA', sum, city.DisplayName);
				}

				city._weight = sum; // jscs:ignore disallowDanglingUnderscores
				// if (sum > 0) {
				//     $log.debug('FINAL', sum, city.DisplayName);
				// }

				return city._weight; // jscs:ignore disallowDanglingUnderscores
			};

			var filterWeightedMatches = function (cityList, nameOrCode) {
				var result = [];

				// var then;
				// if (typeof performance !== 'undefined') {
				//     then = performance.now();
				// }

				for (var i = 0; i < cityList.length; i++) {
					var city = cityList[i];
					if (city != null && weightedMatch(city, nameOrCode) > 0) {
						result.push(city);
					}
				}

				// if (typeof performance !== 'undefined') {
				//     $log.debug('_filterWeightedMatches ['+cityList.length+']:'+
				//         Math.round(1000*(performance.now()-then))+'μs');
				// }

				return result;
			};

			var getMatchingCitiesPromiseExpertBooking = function(nameOrCode, pairCode, filterFn) {
				isExpertBooking = true;
				return getMatchingCitiesPromise(nameOrCode, pairCode, filterFn);
			};

			var getMatchingCitiesPromise = function (nameOrCode, pairCode, filterFn) {
				return getReachableCitiesPromise(pairCode, 1, filterFn).then(function (cityList) {
					return $filter('orderBy')(filterWeightedMatches(cityList, nameOrCode), '-_weight');
				});
			};

			var getMatchingFilteredCities = function (nameOrCode, legIndex, isOrigin) {
				// create a re-callable closure
				var fn = function () {
					return api.getFilteredCityList(legIndex, isOrigin);
				};
				return repromise(fn).then(function (cityList) {
					return $filter('orderBy')(filterWeightedMatches(cityList, nameOrCode), '-_weight');
				});
			};

			var getNitpOriginsPromise = function () {
				return $q.all([getCityListPromise(), getNitpMapPromise()]).then(function (resolved) {
					var list = resolved[0];
					var map = resolved[1];

					var result = [];

					for (var i = 0; i < list.length; i++) {
						var city = list[i];
						if (map.hasOwnProperty(city.Code)) {
							result.push(city);
						}
					}

					return result;
				});
			};

			var getNitpDestinationsPromise = function (cityCode) {
				return $q.all([getCityListPromise(), getNitpMapPromise()]).then(function (resolved) {
					var list = resolved[0];
					var map = resolved[1];

					var result = [];
					var codes = map[cityCode];
					for (var i = 0; i < codes.length; i++) {
						var code = codes[i];
						for (var j = 0; j < list.length; j++) {
							var city = list[j];
							if (city.Code === code) {
								result.push(city);
							}
						}
					}

					return result;
				});
			};

			svc.preloadCitiesExpertBooking = function () {
				isExpertBooking = true;
				svc.preloadCities();
			};

			svc.preloadCities = function() {
				getCityMapPromise();
				getCityPairsPromise();
			};

			svc.isInternational = function (city) {
				return (city != null) && (city.Market === 3);
			};

			svc.pairIsInvalid = function (originCode, destCode) {

				if (!originCode || !destCode) {
					return true;
				}
				var validPairs = getValidPairs(originCode);
				var validPairCodes = [];
				for (var i = 0, len = validPairs.length; i < len; i++) {
					if (validPairs[i] && validPairs[i].Code) {
						validPairCodes.push(validPairs[i].Code);
					}
				}
				if (validPairCodes.indexOf(destCode) < 0) {
					return true;
				}
				return false;
			};

			svc.getAllCities = getCityListPromise;
			svc.getAllCitiesExpertBooking = getCityListPromiseExpertBooking;
			svc.getFilteredCities = getFilteredCityListPromise;
			svc.getReachableCities = getReachableCitiesPromise;
			svc.getCityByCode = getCityByCodePromise;
			svc.getCityByCodeSync = getCityByCodeSync;
			svc.getMatchingCities = getMatchingCitiesPromise;
			svc.getNitpOrigins = getNitpOriginsPromise;
			svc.getNitpDestinations = getNitpDestinationsPromise;
			svc.getMatchingFilteredCities = getMatchingFilteredCities;
			svc.getCityMap = getCityMapPromise;
			svc.getValidPairs = getValidPairs;
			svc.getMatchingCitiesExpertBooking = getMatchingCitiesPromiseExpertBooking;
			svc.getHotelCities = api.getExpediaCityList;
			svc.searchCarLocations = api.searchCarLocations;
			svc.getAllCarLocations = api.getAllCarLocations;
			svc.getCarLocationById = api.getCarLocationById;

			return svc;
		}
	]);

})(angular);
;
(function (angular) {

	// Ha Customer Selections Service
	// --------------------------------------------
	//
	// * **Class:** HaCustomerSelectionService
	// * **Author:** George Pantazis
	//
	// Service for maintaining customer selections model.

	'use strict';

	var mod = angular.module('haCustomerSelectionsService', []);

	mod.service('haCustomerSelections', ['$rootScope', 'haUtils',
		function ($root, haUtils) {

			// Here, you would either restore the $root from localstorage, or
			// sync it with the server via AJAX. For the sake
			// of the front-end demo, we're simply declaring it here.

			var model = {
				'selections': {
					'tripName': 'Default Trip Name',
					'legs': [],
					'passengers': []
				}
			};

			$.extend($root, model);

			$root.$watch('selections', function () {
				service.updateTotal();
			}, true);

			var service = {

				clearLegs: function () {
					$root.selections.legs = [];
				},

				// Prepopulate legs with dates spaced one week apart.
				createLegs: function (howMany, overwrite) {
					for (var i = 0; i < howMany; i++) {
						if (overwrite || (!overwrite && !$root.selections.legs[i])) {

							if ($root.selections.legs[i] === undefined) {
								$root.selections.legs[i] = {};
							}
						}
					}
				},

				getLeg: function (which) {

					if (!haUtils.isNumber(which) || !$root.selections.legs[which]) {
						return false;
					}

					return $root.selections.legs[which];
				},

				setLegDate: function (which, dateObj) {

					var newDateObj = null;
					var newDate = dateObj ? new Date(dateObj.year, dateObj.month - 1, dateObj.day) : new Date(new Date() * 1 + 1000 * 60 * 60 * 24 * 7 * which);
					var now = new Date();
					var today = new Date(now.getFullYear(), now.getMonth(), now.getDate());

					if (dateObj === undefined) {
						if (which > 0) {
							newDateObj = $root.selections.legs[which - 1 * 1].date;
						}

						if (newDateObj === undefined) {
							newDateObj = {
								year: newDate.getFullYear(),
								month: newDate.getMonth() + 1,
								day: newDate.getDate()
							};
						}

					}
					else {

						newDateObj = dateObj;
						var index = 0;
						angular.forEach($root.selections.legs, function (leg) {
							if (leg.date != null) {
								var legDate = new Date(leg.date.year, leg.date.month - 1, leg.date.day);
								if (index < which) {
									if (legDate > newDate) {
										leg.date = newDateObj;
									}
								}
								else if (index > which) {
									if (legDate < newDate) {
										leg.date = newDateObj;
									}
								}
							}
							index++;
						});
					}


					//  nextLeg = $root.selections.legs[which * 1 + 1],
					//  prevLeg = $root.selections.legs[which * 1 - 1];

					//if (nextLeg) {

					//  var nextDate = new Date(nextLeg.date.year, nextLeg.date.month - 1, nextLeg.date.day);

					//  if (newDate > nextDate) {
					//    nextLeg.date = dateObj;
					//  }

					//}

					//if (prevLeg) {

					//  var prevDate = new Date(prevLeg.date.year, prevLeg.date.month - 1, prevLeg.date.day);

					//  if (newDate > prevDate) {
					//    prevLeg.date = dateObj;
					//  }

					//}

					$root.selections.legs[which].date = newDateObj;

					// If the user manually sets the date to before today, go ahead and reset it to today.
					// Remove this conditional if time-travel becomes a possibility in the future.
					if (newDate < today) {
						setTimeout(function () {
							service.setLegDate(which, {
								year: today.getFullYear(),
								month: today.getMonth() + 1,
								day: today.getDate()
							});
						}, 0);
					}

				},

				// As we don't currently have pricing data, this is mocked to a fixed
				// price. It should update the total dynamically as $root.selections changes.
				updateTotal: function () {
					$root.selections.currentTotal = 1234.56;
				},

				updateLegDates: function () {

					if ($root.selections.legs && $root.selections.legs.length > 1) {
						var index = $root.selections.legs.length;
						for (var i = 0; i <= index - 2; i++) {
							var currentLeg = $root.selections.legs[i];
							var nextLeg = $root.selections.legs[i + 1];

							if (currentLeg.date != null && nextLeg.date != null) {
								var currentLegDate = new Date(currentLeg.date.year, currentLeg.date.month - 1, currentLeg.date.day);
								var nextLegDate = new Date(nextLeg.date.year, nextLeg.date.month - 1, nextLeg.date.day);
								if (nextLegDate < currentLegDate) {
									nextLeg.date = currentLeg.date;
								}
							}
						}
					}
				}


			};

			return service;
		}
	]);

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haDataCacheService', ['haHttpService'])

	.service('haDataCacheService', [
		'haHttpService',
		'$cacheFactory',

		function (http, $cacheFactory) {
			var haDataCache = $cacheFactory.get('haDataCache');

			return {
				get: function (url) {
					return http.GET(url, {
						cache: haDataCache
					});
				}
			};
		}
	]);

})(angular);
;
(function (angular) {

	// Ha Favorites Service
	// --------------------------------------------
	//
	// * **Class:** haFavoritesService
	// * **Author:** George Pantazis
	//
	// Service for loading the user's favorites. This is distinct from the user
	// object (see: ha-user-service), as favorites may exist for unauthenticated
	// users.

	'use strict';

	var mod = angular.module('haFavoritesService', []);

	mod.service('haFavorites', ['$rootScope',
		function ($rootScope) {

			// Mock model. Replace this with an $http call to retrieve user data
			// during integration with the HA API infrastructure.

			var model = [{
				'title': 'Waimoku Falls Trail',
				'photo': '/Content/assets/common/images/demo-nav-wishlist1.jpg',
				'location': 'Haleakala National Park'
			}];

			var service = {

				// For demonstration, this simply restores a mock set of favorites.
				// In integration, this would write to localstorage and sync with the server
				// via API services (Angular $http).

				updateFavorites: function () {
					$rootScope.favorites = model;
					return service;
				}

			};

			return service;
		}
	]);

})(angular);
;
(function (angular) {

	// Hawaiian Global Utilities
	// =========================
	//
	// * **Class:** haGlobals
	// * **Author:** Nathan Probst
	//
	// A utility class to access global page scope without
	// declaring globals in code.

	'use strict';

	var module = angular.module('haGlobalsModule', []);

	module.factory('haGlobals', ['$window', function ($window) {

		return function (names, cb, requireAll) {
			// NEP: FIXME: Really, we should be stuffing data into a _namespace_.
			names = [].concat(names);
			requireAll = !!requireAll;

			if ((names.length === 0) || (cb == null)) {
				return;
			}

			var globals = [];
			var foundSome = false;
			var foundAll = true;
			angular.forEach(names, function (name) {
				var found = ((typeof $window[name] !== 'undefined') && ($window[name] !== null));
				foundSome = foundSome || found;
				foundAll = foundAll && found;
				globals.push($window[name]);
			});

			// Only callback if global found
			if ((requireAll && foundAll) || (!requireAll && foundSome)) {
				cb.apply(null, globals);
			}
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Help And Tips Service
	// --------------------------------------------
	//
	// * **Class:** HaHelpAndTipsService
	// * **Author:** George Pantazis
	//
	// Service for asynchronously retrieving help and tips data for a given city.

	'use strict';

	var mod = angular.module('haHelpAndTipsService', []);

	mod.service('haHelpAndTips', function () {

		// Mock model. Replace this with an $http call to retrieve API data
		// during integration with the HA API infrastructure.

		var model = {
			'cityName': 'Kahului, Maui',
			'months': [{
				'sunnyPct': '80',
				'tempLow': '70F',
				'tempHigh': '84F'
			}, {
				'sunnyPct': '90',
				'tempLow': '70F',
				'tempHigh': '84F'
			}, {
				'sunnyPct': '90',
				'tempLow': '70F',
				'tempHigh': '84F'
			}, {
				'sunnyPct': '90',
				'tempLow': '70F',
				'tempHigh': '84F'
			}, {
				'sunnyPct': '90',
				'tempLow': '70F',
				'tempHigh': '84F'
			}, {
				'sunnyPct': '90',
				'tempLow': '70F',
				'tempHigh': '84F'
			}, {
				'sunnyPct': '90',
				'tempLow': '70F',
				'tempHigh': '84F'
			}, {
				'sunnyPct': '90',
				'tempLow': '70F',
				'tempHigh': '84F'
			}, {
				'sunnyPct': '90',
				'tempLow': '70F',
				'tempHigh': '84F'
			}, {
				'sunnyPct': '90',
				'tempLow': '70F',
				'tempHigh': '84F'
			}, {
				'sunnyPct': '90',
				'tempLow': '70F',
				'tempHigh': '84F'
			}, {
				'sunnyPct': '90',
				'tempLow': '70F',
				'tempHigh': '84F'
			}]
		};

		var service = {

			// $http request would occur here; dependencies on this service should utilize
			// the `cb` to pass a function, and expect results asynchronously.

			// I would reccommend that some manner of cacheing be employed if the same city is requested;
			// this will enable the FE modules using this service to be highly modular without having
			// to worry about overpinging the server.

			getData: function (city, cb) {

				if (city === 'OGG') {
					model.cityName = 'Kahului, Maui';
				} else if (city === 'HNL') {
					model.cityName = 'Honolulu';
				} else {
					model.cityName = 'Default Data';
				}

				cb(model);
			}

		};

		return service;
	});

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haHttpService', [])
	.factory('requestedWithInterceptor', [
		'$location', '$rootScope', function ($location, $rootScope) {
			return {
				request: function (config) {

					if (!/\/\//.test(config.url)) {
						config.headers['X-Requested-With'] = 'XMLHttpRequest';
						config.headers.csrf = $rootScope.csrf;
					}

					return config;
				},
				response: function (response) {
					if (response && response.data && response.data.ErrorType) {
						if (response.data.ErrorType === 'TabError') {
							//$location.path and $lcoation.url methods are not working in few pages so used native window.lcoation.href
							window.location.href = '/book/error?ErrorType=TabError';
							return;
						} else if (response.data.ErrorType === 'SessionTimeOut') {
							window.location.href = '/book/error?ErrorType=SessionTimeOut';
							return;
						}
					}
					return response;
				}
			};
		}
	])
	.factory('sessionLostInterceptor', ['$q', '$rootScope', function($q, $rootScope){
		return {
			responseError: function(response) {
				if (response && response.status === 401) {
					$rootScope.$broadcast('$sessionLostEvent');
				}
				return $q.reject(response);
			}
		}
	}])
	.config([
		'$httpProvider', function ($httpProvider) {
			$httpProvider.interceptors.push('requestedWithInterceptor');
			$httpProvider.interceptors.push('sessionLostInterceptor');
		}
	])
	.factory('haHttpService', [
		'$log', '$http', '$q', function ($log, $http, $q) {
			return {
				GET: function (url, config) {
					config = config || {};
					if (typeof $log.debug === 'function') {
						$log.debug('GET ' + url);
					}
					return $http.get(url, config)
					.error(function (data, status /*, headers, config*/) {
						$log.warn('[' + status + '] GET ' + url, data);
					});
				},

				GETPARALLEL: function (urls) {
					var deferred = $q.defer();
					var promises = [];

					angular.forEach(urls, function (url) {
						promises.push($http.get(url));
					});

					$q.all(promises).then(
						function (responses) {
							deferred.resolve(responses);
						},
						function () {
							deferred.reject();
						}
					);

					return deferred.promise;
				},

				POST: function (url, data, config) {
					config = config || {};
					if (typeof $log.debug === 'function') {
						$log.debug('POST ' + url);
					}
					return $http.post(url, data, config)
					.error(function (data, status /*, headers, config*/) {
						$log.warn('[' + status + '] POST ' + url, data);
					});
				},

				PUT: function (url, data, config) {
					config = config || {};
					if (typeof $log.debug === 'function') {
						$log.debug('PUT ' + url);
					}
					return $http.put(url, data, config)
					.error(function (data, status /*, headers, config*/) {
						$log.warn('[' + status + '] PUT ' + url, data);
					});
				},

				DELETE: function (url, config) {
					config = config || {};
					if (typeof $log.debug === 'function') {
						$log.debug('DELETE ' + url);
					}
					return $http['delete'](url, config)
					.error(function (data, status /*, headers, config*/) {
						$log.warn('[' + status + '] DELETE ' + url);
					});
				}
			};
		}
	])
	.directive('form', [
		'$rootScope', function ($rootScope) {
			return {
				restrict: 'E',
				link: function ($scope, $el) {
					if ($el.length && $rootScope.csrf) {
						$el.append($('<input type="hidden" name="__RequestVerificationToken" value="' + $('body > [name="__RequestVerificationToken"]').val() + '" />'));
					}
				}
			};
		}
	]);

})(angular);
;
(function (angular) {

	'use strict';

	var mod = angular.module('haModalService', []);

	mod.factory('haModal', ['$document', '$compile', '$rootScope', 'haConfig', '$controller', '$timeout',
		function ($document, $compile, $rootScope, haConfig, $controller, $timeout) {
			var defaults = {
				id: null,
				template: null,
				templateUrl: null,
				title: 'Default Title',
				heading:'Default Heading',
				backdrop: true,
				success: { label: 'OK', fn: null },
				cancel: { label: 'Close', fn: null },
				controller: null, //just like route controller declaration
				backdropClass: 'modal-backdrop',
				defaultContent: null,
				modalClass: 'modal',
				modalLock: false,
				size: '',
				verticalCenter: false,
				mobileVerticalCenter: false
			};

			var body = $document.find('body');

			function dialog(/*optional*/templateUrl, options, passedInLocals) {
				var loadingSpinner = angular.element('<div class="ha-loading-spinner fixed"><div><div></div></div></div>');
				body.append(loadingSpinner);

				var target = $(document.activeElement),
					scrollPos = window.pageYOffset,
					subsequentModalOpened = false,
					closed = false;

				// Handle arguments if optional template isn't provided.
				if (angular.isObject(templateUrl)) {
					passedInLocals = options;
					options = templateUrl;
				} else {
					options.templateUrl = templateUrl;
				}

				options = angular.extend({}, defaults, options); //options defined in constructor

				var idAttr = options.id ? ' id="' + options.id + '" ' : '';

				// Custom Event for Analytics
				document.body.dispatchEvent(new CustomEvent('ModalOpened', { detail: options.id }));
				$rootScope.$broadcast('cancelTooltip');

				var modalBody = (function () {
					if (options.template) {
						if (angular.isString(options.template)) {
							// Simple string template
							return options.template;
						} else {
							// jQuery/JQlite wrapped object
							return options.template.html();
						}
					} else {
						// Template url
						return '<div ng-include="\'' + options.templateUrl + '\'"></div>';
					}
				})();
				//We don't have the scope we're gonna use yet, so just get a compile function for modal

				var modalEl = '';
				var backdropEl = '';
				var modalLock = options.modalLock;

				if (modalLock) {
					modalEl = angular.element(
						'<div class="ha-modal ' + options.modalClass + ' fade"' + idAttr + ' tabindex="0">' +
						'<div class="modalContainer modalLock ' + options.size + '">' +
						modalBody +
						'</div>' +
						'</div>');
					backdropEl = angular.element('<div class="modal-backdrop">');
				} else {
					modalEl = angular.element(
						'<div class="ha-modal ' + options.modalClass + ' fade"' + idAttr + ' tabindex="0">' +
						'<div class="modalContainer ' + options.size + '">' +
						'<a class="close-modal-icon" ng-click="$modalCancel()" href="" role="button"><i class="close ha-icon icon-close"></i><span class="sr-only" scs-text="forms.close"></span></a>' +
						modalBody +
						'</div>' +
						'</div>');
					backdropEl = angular.element('<div class="modal-backdrop" ng-click="$modalCancel($event)">');
				}

				var focusables, inputs;
				var collectFocusables = function () {
					focusables = $(modalEl[0]).find(':focusable');
					inputs = $(modalEl[0]).find('input[type=text], input[type=email], textarea, select');
					if (inputs.length > 0) {
						// do not focus inputs for IE9, bc it suppresses placeholder shim
						if (!$('html').hasClass('lte-ie9')) {
							$(inputs[0]).focus();
						}
					}
					else if (!$rootScope.isMobile) {
						$(focusables[0]).focus();
					}
				};

				var setVerticalCenter = function () {
					var listner = scope.$watch(
						function () {
							return modalEl.find('.modalContainer').height();
						},
						function (newValue, oldValue) {
							if (newValue !== 0) {
								var modalContainer = modalEl.find('.modalContainer');
								var marginTop = (modalContainer.outerHeight() / 2) * -1;
								modalContainer.css({ 'top': '50%', 'margin-top': marginTop });
								listner(); // remove watcher
							}
						}
					);
				};

				var handleTabPressed = function (event) {
					if (event.keyCode === 9) {
						// refresh the focusable list as it might have changed
						focusables = $(modalEl[0]).find(':focusable').get();

						// trap tab key within modal
						var isfirstfocusable = document.activeElement === focusables[0];
						var islastfocusable = document.activeElement === focusables[focusables.length - 1];

						if (event.shiftKey && isfirstfocusable) {
							// if focused on first element and pressing -shifttab, cycle around backwards
							event.preventDefault();
							focusables[focusables.length - 1].focus();
						} else if (!event.shiftKey && islastfocusable) {
							// if focused on last element and pressing tab, cycle around
							event.preventDefault();
							focusables[0].focus();
						}
					}
				};

				var handleEscPressed = function (event) {
					if (event.keyCode === 27 && !modalLock) {
						$(document.activeElement).blur();
						scope.$modalCancel(event);
					}
				};

				var closeFn = function () {
					// debounce closeFn
					if (closed) {
						return;
					} else {
						closed = true;
					}
					body.unbind('keydown', handleEscPressed);
					body.unbind('keydown', handleTabPressed);
					backdropEl.removeClass('fade in');

					//loadingSpinner.remove();
					modalEl.removeClass('in');
					$timeout(function () {
						modalEl.remove();
						body.removeClass('modal-active');

						// if we have multiple modals, ensure modalactive remains true until they are all closed
						$rootScope.modalsopen--;
						if ($rootScope.modalsopen == 0) {
							$rootScope.modalactive = false;
						}

						if ($rootScope.isMobile && !subsequentModalOpened) {
							var bodyMargin = parseFloat($('body').css('margin-top'));
							scrollPos = bodyMargin * -1;
							// reset margin
							$('body').css({ 'margin-top': 0 });
							window.scrollTo(0, scrollPos);
						}
						if (options.backdrop) {
							backdropEl.remove();
						}
					}, 300);
				};

				body.bind('keydown', handleEscPressed);
				body.bind('keydown', handleTabPressed);

				var ctrl;
				var locals;
				var scope = options.scope || $rootScope.$new();

				if (options.extendScope) {
					angular.extend(scope, options.extendScope);
				}

				if (!$rootScope.modalsopen) {
					$rootScope.modalsopen = 1;
				} else {
					$rootScope.modalsopen++;
				}
				$rootScope.modalactive = true;
				scope.$defaultContent = options.defaultContent;
				scope.$title = options.title;
				scope.$heading = options.heading;
				scope.$modalClose = closeFn;				

				// listen for broadcasted close event
				scope.$on('$modalCancel', function (event, idString) {
					if (idString && idString.length) {
						if (idString === options.id) {
							scope.$modalClose();
						}
					}
					else {
						scope.$modalClose();
					}
				});
				// for handling subsequent modals on mobile
				scope.$on('$modalOpened', function (event, idString) {
					if (idString && idString.length && idString !== options.id) {
						subsequentModalOpened = true;
					}
				});

				scope.$modalCancel = function ($event) {
					var modalcontainer = modalEl.find('.modalContainer');
					if ($event && $event.type === 'click' && (!$.contains(modalcontainer[0], $event.target) || modalcontainer.is($event.target) || !$.contains(document, $event.target))) {
						return;
					}
					var callFn = options.cancel.fn || closeFn;

					scope.$modalClose();
					$timeout(function () {
						scope.$emit('haModalClosed', options.id);
						if (!$rootScope.isMobile && target && target[0] && target[0].tagName !== 'g') {
							// set back to the element which triggered the modal
							target.focus();
							//document.body.scrollTop = scrollPos;
						}
					}, 300);

					callFn.call(this);
				};				
				scope.$modalSuccess = function () {
					var callFn = options.success.fn || closeFn;
					callFn.call(this);
					scope.$modalClose();
				};
				scope.$modalSuccessLabel = options.success.label;
				scope.$modalCancelLabel = options.cancel.label;

				if (options.controller) {
					locals = angular.extend({ $scope: scope }, passedInLocals);
					ctrl = $controller(options.controller, locals);
					// Yes, ngControllerController is not a typo
					modalEl.contents().data('$ngControllerController', ctrl);
				}

				$rootScope.$on('closeModal', function () {
					scope.$modalCancel();
				});

				$compile(modalEl)(scope);
				$compile(backdropEl)(scope);
				body.append(modalEl);
				body.addClass('modal-active');

				if ($rootScope.isMobile) {
					// allows body to remain fixed to prevent scrolling of content underneath modal,
					// while maintaining scroll position on page.
					var bodyMargin = parseFloat($('body').css('margin-top'));
					if (bodyMargin >= 0) {
						$('body').css({ 'margin-top': scrollPos * -1 });
					}
					// auto-pad .modal-main depending on height of .modal-header
					if (modalEl.find('.modal-header').length) {
						var headerHeight = modalEl.find('.modal-header').outerHeight();
						modalEl.find('.modal-main').css({ paddingTop: headerHeight });
					}
				}
				$rootScope.$broadcast('$modalOpened', options.id);

				if (options.backdrop) {
					body.append(backdropEl);
					$timeout(function () {
						backdropEl.addClass('fade in');
					}, 0);
				}

				// click outside to close
				// do not use ng-click on .ha-modal because it breaks modals on iPad
				$('.ha-modal').on('click', function (e) {
					e.stopPropagation();
					if ($(e.target).hasClass('ha-modal') && !options.modalLock) {
						scope.$modalCancel();
					}
				});

				$timeout(function () {
					modalEl.addClass('in');
					loadingSpinner.addClass('ng-hide');

					collectFocusables();
					if (options.callback) {
						options.callback(modalEl);
					}

					if (!$rootScope.isMobile && options.verticalCenter) {
						setVerticalCenter();
					}
					if ($rootScope.isMobile && options.mobileVerticalCenter) {
						modalEl.addClass('mobile-vertical-center');
					}
				}, 100);

				return scope;
			}

			dialog.openPriceCalendar = function (legs, adults, children) {
				var options = {
					id: 'FlexiblePriceView',
					backdrop: 'true',
					extendScope: {
						legs: legs,
						adults: adults || 1,
						children: children || 0
					}
				};
				dialog(haConfig.getTemplateUrl('/Book/FlightSearch/ha-flexible-price-modal.html'), options);
			};

			window.haModal = dialog;
			return dialog;
		}]);
})(angular);
;
(function (angular) {

	// Ha Search Cache Service
	// --------------------------------------------
	//
	// * **Class:** HaSearchCacheService
	// * **Author:** George Pantazis
	//
	// Service for maintaining customer recent search model.

	'use strict';

	var mod = angular.module('haSearchCacheService', []);

	mod.service('haSearchCache', ['$rootScope',
		function ($rootScope) {

			// Here, you would either restore from localstorage, or
			// sync it with the server via AJAX. For the sake
			// of the front-end demo, we're simply declaring it here.

			// Once I've assembled the components into a booking module, I'll
			// hook this up to localStorage and remove this fake model.

			var model = {
				'searchCache': [{
					'tripName': 'Trip To Honolulu',
					'legs': [{
						'origin': 'SFO',
						'destination': 'HNL',
						'date': {
							'year': 2013,
							'month': 9,
							'day': 10
						},
						'time': {
							'start': 900,
							'end': 2100
						}
					}]
				}, {
					'tripName': 'Trip To Honolulu',
					'legs': [{
						'origin': 'SFO',
						'destination': 'HNL',
						'date': {
							'year': 2013,
							'month': 9,
							'day': 10
						},
						'time': {
							'start': 900,
							'end': 2100
						}
					}, {
						'origin': 'HNL',
						'destination': 'SFO',
						'date': {
							'year': 2013,
							'month': 9,
							'day': 20
						},
						'time': {
							'start': 900,
							'end': 2100
						}
					}]
				}, {
					'tripName': 'Trip To Hawaii',
					'legs': [{
						'origin': 'SFO',
						'destination': 'HNL',
						'date': {
							'year': 2013,
							'month': 9,
							'day': 10
						},
						'time': {
							'start': 900,
							'end': 2100
						}
					}, {
						'origin': 'HNL',
						'destination': 'OGG',
						'date': {
							'year': 2013,
							'month': 9,
							'day': 15
						},
						'time': {
							'start': 900,
							'end': 2100
						}
					}, {
						'origin': 'OGG',
						'destination': 'SFO',
						'date': {
							'year': 2013,
							'month': 9,
							'day': 20
						},
						'time': {
							'start': 900,
							'end': 2100
						}
					}]
				}]
			};

			$.extend($rootScope, model);

			var service = {

				cacheCurrentSelections: function () {
					$rootScope.searchCache.unshift($rootScope.selections);
				},

				restoreSearch: function (which) {
					if ($rootScope.searchCache[which]) {
						$rootScope.selections = $.extend(true, {}, $rootScope.searchCache[which]);
					}
				}

			};

			return service;
		}
	]);

})(angular);
;
(function (angular) {

	// Ha Segment Service
	// --------------------------------------------
	//
	// * **Class:** haSeatClassService
	// * **Author:** Josh Nielsen
	//
	// Service for flight segment functions


	'use strict';

	var mod = angular.module('haSegmentService', []);

	mod.service('haSegmentService', ['haPassengersService', '$log',
		function ($pax, $log) {

			var service = {

				selectedSegments: [],

				legs: [],

				numFares: function (segment, paxType) {
					var fares = {
						Adult: 0,
						Child: 0,
						Infant: 0
					};

					var relevantTypes = this.getRelevantPaxTypes();
					if ($pax.passengers != null) {
						angular.forEach($pax.passengers, function (pax) {
							if ((pax.Type === 'Child') && (relevantTypes.indexOf(pax.Type) < 0)) {
								fares.Adult++;
							} else {
								fares[pax.Type]++;
							}
						});
					}

					return fares[paxType];
				},

				getRelevantPaxTypes: function (segment) {
					var relevantTypes = [];
					var self = this;

					// return relevants types for a single segment if passes as argument, otherwise return for all segments
					var segments = segment ? [segment] : this.selectedSegments;

					// for each passenger type check to see if there are corresponding fares available in segments
					angular.forEach($pax.getNonInfants(), function (pax) {
						angular.forEach(segments, function (segment) {
							if (self.getFare(segment, pax.Type, true)) {
								if (relevantTypes.indexOf(pax.Type) === -1) {
									relevantTypes.push(pax.Type);
								}
							}
						});
					});

					return relevantTypes;
				},

				getFare: function (segment, passengerType, noCoerce) {
					var fare;
					var self = this;

					// use selectedSeatClass if set otherwise default to first seat class in list
					if (segment != null) {
						var fareClass = this.getFareClass(segment);
						if (segment.BookingFares != null) {
							angular.forEach(segment.BookingFares, function (bookingFare) {
								if (bookingFare.Name.toLowerCase() === fareClass) {
									// try for fareType based on passengerType but fall back to default (Adult) if undefined
									var origFareType = bookingFare.FareTypes[passengerType];

									if (noCoerce) {
										fare = origFareType;
									} else {
										fare = self.getClosestFare(passengerType, bookingFare.FareTypes);
									}
								}
							});
						}
					} else {
						$log.warn('getFare() called with null "segment"');
					}
					return fare;
				},

				getClosestFare: function (passengerType, fares) {
					var paxTypeIdx = $pax.types.indexOf(passengerType);
					for (var i = paxTypeIdx; i >= 0; i--) {
						var fare = fares[$pax.types[i]];
						if (fare) {
							return fare;
						}
					}
				},

				getFareClass: function (segment) {
					// use selected seat class if selection has been made, otherwise default to first (0) seat class
					var fareClass;
					if (segment != null) {
						if ((segment.BookingFares != null) &&
							(segment.BookingFares[0] != null) &&
							(typeof segment.BookingFares[0].Name === 'string')) {
							fareClass = segment.BookingFares[0].Name.toLowerCase();
						}
					} else {
						$log.warn('getFareClass() called with null "segment"');
					}
					var seatClass = segment.selectedSeatClass || fareClass;
					return seatClass;
				},

				getExtraComfortPrice: function (segment) {
					var price = 0;
					if (this.Legs != null) {
						angular.forEach(segment.Legs, function (leg) {
							price += this.getLegExtraComfortPrice(leg);
						});
					}

					return price;
				},

				getLegExtraComfortPrice: function (leg) {
					var price = 0;
					if (leg.SeatSelections != null) {
						angular.forEach(leg.SeatSelections, function (selection) {
							price += selection.price;
						});
					}

					return price;
				}
			};

			return service;
		}
	]);

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haTemplateCache', ['haHttpService'])

	.service('haTemplateCache', [
		'haHttpService',
		'$templateCache',

		function (http, $templateCache) {
			return {
				get: function (url) {
					return http.GET(url, {
						cache: $templateCache
					});
				}
			};
		}
	]);

})(angular);
;
(function (angular) {

	// Ha Login Service
	// --------------------------------------------
	//
	// * **Class:** haUserService
	// * **Author:** George Pantazis
	//
	// Service for loading the logged-in user's state, or logging the user out.

	'use strict';

	var mod = angular.module('haUserService', []);

	mod.service('haUser', ['$rootScope',
		function ($rootScope) {

			// Mock model. Replace this with an $http call to retrieve user data
			// during integration with the HA API infrastructure.

			var model = {
				'firstName': 'John',
				'lastName': 'Doe',
				'photo': '/Content/assets/common/images/demo-nav-account-pic-icon.jpg',
				'haMiles': '14552',
				'upcomingTrips': [{
					'departTime': 1386375432426,
					'arriveTime': 1386375432426,
					'flights': [123, 456, 789],
					'onTime': true
				}, {
					'departTime': 1386375432426,
					'arriveTime': 1386375432426,
					'flights': [123, 456, 789],
					'onTime': true
				}],
				'savedTrips': [{
					'tripName': 'Trip to Maui',
					'tripType': 'Package',
					'tripLink': '#',
					// If flight price has updated since the last visit.
					'newPrice': false,
					'savedDate': 1386375432426,
					'departDate': 1386375432426,
					'returnDate': 1386375432426,
					'otherPassengers': [
						'Charlie Becket',
						'Mako Becket',
						'Brad Becket'
					]
				}, {
					'tripName': 'Trip to Honolulu',
					'tripType': 'Package',
					'tripLink': '#',
					// If flight price has updated since the last visit.
					'newPrice': true,
					'savedDate': 1386375432426,
					'departDate': 1386375432426,
					'returnDate': 1386375432426,
					'otherPassengers': [
						'Charlie Becket'
					]
				}, {
					'tripName': 'Trip to Big Island',
					'tripType': 'Package',
					'tripLink': '#',
					// If flight price has updated since the last visit.
					'newPrice': true,
					'savedDate': 1386375432426,
					'departDate': 1386375432426,
					'returnDate': 1386375432426,
					'otherPassengers': [
						'Charlie Becket',
						'Brad Becket'
					]
				}],
				'eCerts': [{
					'title': '30% Off',
					'subtitle': 'Mainland to Hawaii Islands',
					'travelStart': 1386375432426,
					'travelEnd': 1386375432426,
					'bookBy': 1386375432426
				}]
			};

			var service = {

				// For demonstration, this simply restores a mock user. In integration this would primarily
				// be used for updating the $root.user object after a successful login $http request.

				updateUser: function () {
					$rootScope.user = model;
					return service;
				},

				// For demonstration, this simply removes the $root.user object. In integration,
				// this should hit a logout REST service and only on logout success clear the user object.

				logout: function () {
					delete $rootScope.user;
					return service;
				}

			};

			// This should call on instantiation, in order to load the current user into,
			// Angular's $rootScope, if one exists.

			service.updateUser();

			return service;
		}
	]);

})(angular);
;
(function (angular) {

    // Hawaiian Global Utilities
    // =========================
    //
    // * **Class:** haUtils
    //
    // A global store for common, reusable JS methods.

    'use strict';

    //////////////////////////
    // PROTOTYPE AUGMENTATIONS
    //////////////////////////

    // STRING
    if (!String.prototype.format) {
        String.prototype.format = function () {
            var args = arguments;
            return this.replace(/{(\d+)}/g, function (match, number) {
                return typeof args[number] !== 'undefined' ? args[number] : match;
            });
        };
    }


    var module = angular.module('haUtilsModule', ['haViewModelModule']);

    module.factory('haUtils', [
		'$rootScope',
		'$log',
		'$window',
		'$locale',		
		'haViewModelSvc',
		'haConfig',
		function ($rootScope, $log, $window, $locale, haViewModelSvc, haConfig) {

		    var svc = {
		        // SEE: http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
		        debounce: function (func, threshold, execAsap) {
		            var timeout;

		            return function debounced() {
		                var self = this;
		                var args = arguments;

		                function delayed() {
		                    if (!execAsap) {
		                        func.apply(self, args);
		                    }
		                    timeout = null;
		                }

		                if (timeout) {
		                    clearTimeout(timeout);
		                } else if (execAsap) {
		                    func.apply(self, args);
		                }

		                timeout = setTimeout(delayed, threshold || 100);
		            };
				},

		        safeApply: function ($scope, fn) {
		            if ((typeof $scope.$id !== 'number') && (typeof fn === 'function')) {
		                throw new Error('safeApply expects a scope and a function.');
		            }

		            var phase = $rootScope.$$phase;
		            if (phase === '$apply' || phase === '$digest') {
		                fn();
		            } else {
		                $scope.$apply(fn);
		            }
		        },

		        closestInArray: function (arr, num) {

		            var closest;
		            var closestDiff = Infinity;

		            for (var i in arr) {

		                var diff = Math.abs(arr[i] - num);

		                if (Math.abs(arr[i] - num) < closestDiff) {
		                    closestDiff = diff;
		                    closest = arr[i];
		                }
		            }

		            return closest;
		        },

		        getChildrenPositions: function ($el, $children) {

		            var bounds = $el.get(0).getBoundingClientRect().left;
		            var positions = [];

		            $children.each(function () {
		                positions.push(this.getBoundingClientRect().left - bounds);
		            });

		            return positions;
		        },

		        indexOf: function (array, obj) {
		        	if (!array || !obj) {
		        		return -1;
		        	}

		        	for (var i = 0; i < array.length; i++) {
		        		if (array[i] === obj) {
		        			return i;
		        		}
		        	}
		        	return -1;
		        },

		        inIframe: function () {
					try {
						return $window.self !== $window.top;
					} catch (e) {
						return true;
					}
				},

		        isNumber: function (n) {
					return !isNaN(parseFloat(n)) && isFinite(n);
		        },

		        leftPad: function (n, targetLength) {
		            var output = n + '';
		            while (output.length < targetLength) {
		                output = '0' + output;
		            }
		            return output;
		        },

		        objRef: function (obj, str) {
		            str = str.split('.');
		            for (var i = 0; i < str.length; i++) {
		                if (!obj || obj[str[i]] === undefined) {
		                    return undefined;
		                }
		                obj = obj[str[i]];
		            }
		            return obj;
		        },

		        objSet: function (obj, str, val) {
		            str = str.split('.');
		            while (str.length > 1) {
		                obj = obj[str.shift()];
		            }
		            obj[str.shift()] = val;
		            return val;
		        },

		        readCookie: function (name) {
		            name += '=';
		            for (var ca = document.cookie.split(/;\s*/), i = ca.length - 1; i >= 0; i--) {
		                if (!ca[i].indexOf(name)) {
		                    return ca[i].replace(name, '');
		                }
		            }
		        },

		        getFlightQueryModelCookie: function () {
		            var checkCookie = this.readCookie('FlightQueryModelCookie2');
		            if (checkCookie !== undefined) {
		                $log.debug('FlightQueryModelCookie2: Found');
		                checkCookie = decodeURIComponent(checkCookie);
		                var fsModel = parseFSModel(checkCookie);
		                if (fsModel) {
		                    return fsModel;
		                }
		            }
		            $log.debug('FlightQueryModelCookie2: null');
		            return null;
		        },

		        getFlightQueryModelRecentCookie: function () {
		            var checkCookie = this.readCookie('FlightQueryModelRecentCookie2');
		            if (checkCookie !== undefined) {
		                $log.debug('getFlightQueryModelCookie2: Found');
		                checkCookie = decodeURIComponent(checkCookie);
		                var cookieList = [];
		                var recentList = checkCookie.split(',');
		                angular.forEach(recentList, function (item) {
		                    var fsModel = parseFSModel(item);
		                    if (fsModel) {
		                        cookieList.push(fsModel);
		                    }
		                });
		                if (cookieList.length > 0) {
		                    return cookieList;
		                }
		            }
		            $log.debug('getFlightQueryModelCookie2: null');
		            return null;
		        },

		        getReshopFlightQueryModelCookie: function () {
		            var checkCookie = this.readCookie('ReshopFlightQueryModelCookie');
		            if (checkCookie !== undefined) {
		                checkCookie = decodeURIComponent(checkCookie);
		                return JSON.parse(checkCookie);
		            }
		            return null;
		        },

		        isEN: function () {
		            var $body = angular.element('body');

		            return $body.hasClass('en') || $body.hasClass('en-au') || $body.hasClass('en-nz') || $body.hasClass('en-us');
		        },

		        isJP: function () {
		            return angular.element('body').hasClass('ja-jp');
		        },

		        isKR: function () {
		            return angular.element('body').hasClass('ko-kr');
		        },

		        isCN: function () {
		            return angular.element('body').hasClass('zh-cn');
		        },

		        isTW: function () {
		            return angular.element('body').hasClass('zh-tw');
		        },

		        ensureRootScope: function () {
		            return $rootScope.HA || ($rootScope.HA = {});
		        },

		        ensureScope: function (memberName, scope) {
		            return scope[memberName] || (scope[memberName] = {});
		        },

		        isLocalDev: function () {
		            return ($window.location.hostname.indexOf('local') === 0);
		        },

		        // Use ng-model="scope.path" on a partial to attach it to the containing scope correctly.
		        attachNgModelAttrToScopeAsVM: function ($scope, $attrs, initFn) {
		            if ($attrs.ngModel != null) {
		                $attrs.$observe('ngModel', function (value) {
		                    if (value != null) {
		                        $scope.$watch(value, function (model) {
		                            if (model != null) {
		                                $scope.VM = model;
		                                if (initFn != null) {
		                                    initFn($scope.VM);
		                                }
		                            }
		                        });
		                    }
		                });
		            }
		        },

		        // Use ha-view-model="vmName" to inject the server's ViewModel into scope as 'VM' (by convention).
		        // Respect and use any inherited $scope.VM
		        attachViewModelToScopeAsVM: function ($scope, vmName, initFn) {
		            return haViewModelSvc.get(vmName).then(function (VM) {
		                if ($scope.VM == null) {
		                    $scope.VM = VM;
		                }
		                if ($scope.VM != null && initFn != null) {
		                    initFn($scope.VM);
		                }
		            });
		        },

		        attachPartialVM: function ($scope, $attrs, vmName, defaultModel, initFn) {
		            // If we have ngModel="VM.Something", then we should use that as the local VM.
		            if ($attrs.ngModel != null) {
		                svc.attachNgModelAttrToScopeAsVM($scope, $attrs, initFn);
		            }
		                // If we have a specified vmName, the we should use the data from ha-view-model="vmName".
		            else if (vmName != null) {
		                svc.attachViewModelToScopeAsVM($scope, vmName, initFn);
		            }
		                // Else we should use the controller-provided defaultModel to avoid runtime error.
		            else {
		                $scope.VM = defaultModel || {};
		                if (initFn != null) {
		                    initFn($scope.VM);
		                }
		            }
		        },

		        // SC Strings look like this:
		        // "<image mediapath="/Images/Brand/Airplanes/Airbus-A330/Interior/Front Cabin/a330-frontcabin-fltattendants-300x170" mediaid="{469511C8-454F-4188-B645-FD5BF91E4E61}" src="~/media/469511C8454F4188B645FD5BF91E4E61.ashx?20140731T0203257678" />"
		        // And are resolvable like this:
		        // /~/media/Images/Brand/Airplanes/Airbus-A330/Interior/Front Cabin/a330-frontcabin-fltattendants-300x170.ashx
		        // If mediapath is unavailble, use src
		        getImageFromSiteCoreString: function (scString) {
		            var $image = $(scString);
		            var mp = $image.attr('mediapath');
		            if (mp) {
		            	return [HA.cdn, '/~/media', mp, '.ashx'].join('');
		            }

		            var src = $image.attr('src');
		            if (src) {
		            	return [HA.cdn, '/', src].join('');
		            }
		        },

		        getImageObjectFromSiteCoreString: function (scString) {
		            var $image = $(scString);
		            var mp = $image.attr('mediapath');
		            var alt = $image.attr('alt');
		            var width = $image.attr('width');
		            var height = $image.attr('height');

		            return {
		                src: [HA.cdn, '/~/media', mp, '.ashx'].join(''),
		                alt: alt,
		                width: width,
		                height: height
		            };
		        },

		        webtrends: {
		            token: function (context) {
		                var pageName = $window.$pageName.replace(' ', '');
		                var country = $window.$langCode.substr(-2);
		                return ['HAWAIIANAIR', country, context, pageName].join('_');
		            },
		            tokenV2: function(context) {
		                var country = $window.$langCode.substr(-2);
		                return ['HAWAIIAN-', country, '.TPS.BRAND.',context].join('');

		            }
		        },

		        parseLegs: function (legs) {
		            return legs && legs.split(',').map(function (leg) {
		                var m = /([A-Za-z]{3})-?([A-Za-z]{3})?[ +-]?(\d\d\d\d-\d\d-\d\d)?/.exec(leg);
		                if (!m) {
		                    return;
		                }

		                return {
		                    origin: { code: m[1].toUpperCase() },
		                    destination: { code: m[2].toUpperCase() },
		                    departDate: m[3] && moment(m[3], 'YYYY-MM-DD').toDate()
		                };
		            });
				},

				splitUrl: function (urlString) {
					var urlInfo = {
						url: urlString,
						params: []
					};

					if (urlString) {
						var urlSplit = urlString.split('?');
						if (urlSplit.length > 1) {
							urlInfo = {
								url: urlSplit[0],
								params: urlSplit[1].split('&')
							};
						}
					}

					return urlInfo;
				},

		        querystring: function (name) {
		            if (!urlParams) {
		                parseQS();
		            }
		            return urlParams[name];
		        },

		        createQueryString: function (object, carRentalObj, urlParams) {
		            var params = [];
		            angular.forEach(object, function (val, key) {
		                params.push(key + '=' + encodeURI(val));
					});

					if (carRentalObj) {
						var discountQuery = encodeURI(this.createCarRentalQuery(carRentalObj));

						if (discountQuery) {
							params.push('supplierBenefits=' + discountQuery);
						}
					}

					if (urlParams) {
						for (var i = 0; i < urlParams.length; i++) {
							params.push(urlParams[i]);
						}
					}
		            return '?' + params.join('&');
				},

				createCarRentalQuery: function (carRentalObj) {
					var carRentalParams = [];
					angular.forEach(carRentalObj, function (discountCodes, companyCode) {
						angular.forEach(discountCodes, function (discountValue, discountType) {
							if (discountValue) {
								carRentalParams.push(companyCode + "|" + discountType + "|" + discountValue);
							}
						});
					});
					return carRentalParams.join(",");
				},

				// Use language to get locale for ABG query string
				getLocale: function(language) {
					var locale = '';
					// language = en, en-au, en-nz, ja-jp, ko-kr, zh-cn, zh-tw
					switch (language) {
						case 'ja-jp':
							locale = 'jp';
							break;
						case 'zh-cn':
							locale = 'zh';
							break;
						case 'zh-tw':
							locale = 'zh_hk';
							break;
						case 'ko-kr':
							locale = 'ko';
							break;
						default:
							locale = 'en';
							break;
					}
					return locale;
				},


		        localStorageSet: function (key, object) {
		            if ($window.localStorage) {
		                $window.localStorage.setItem(key, object);
		            }
		        },

		        localStorageGet: function (key) {
		            var saved;
		            if ($window.localStorage) {
		                saved = $window.localStorage[key];
		            }

		            if (saved) {
		                return saved;
		            } else {
		                return null;
		            }
		        },

		        injectScriptDependency: function (parentElem, scriptLink) {
		            var scriptTag = angular.element(document.createElement('script'));
		            scriptTag.attr('charset', 'utf-8');
		            scriptTag.attr('src', scriptLink);
		            parentElem.prepend(scriptTag);
		        },

		        /* changes "{0} membership will start on {1} and expire on {2}."
						 to "Jamie's membership will start on 11/27/14 and expire on 11/27/15"
						 */
		        formatDynamicString: function (string, replacementArray) {
		            if (string) {
		                return string.replace(/({\d})/g, function (j) {
		                    if (j) {
		                        return replacementArray[j.replace(/{/, '').replace(/}/, '')];
		                    }
		                });
		            }
		        },

		        checkForInfant: function (vm) {
					var hasInfant = false;
					for (var x = 0; x < vm.Travellers.length; x++) {
						if (vm.Travellers[x].Type == 'INF') {
							hasInfant = true;
						}
					}
					return hasInfant;
				},

				checkIfTicketedInfant: function (vm) {
					var isTicketed = false;
					for (var x = 0; x < vm.Travellers.length; x++) {
						for (var y = 0; y < vm.Tickets.length; y++) {
							if (vm.Travellers[x].Type == 'INF' && (vm.Travellers[x].DisplayName === vm.Tickets[y].TravellerName) && vm.Tickets[y].ETicket.length > 0) {
								isTicketed = true;
							}
						}
					}
					return isTicketed;
				},

				removeDuplicatesFromArray: function (arrayToCheck, stringPropertyToCompare) {
					return arrayToCheck.filter(
						function (a) { return !this[a[stringPropertyToCompare]] ? this[a[stringPropertyToCompare]] = true : false; }, {}
					);
				},

				getStandardLocale: function () {
					var localeSplit = $locale.id.split('-');
					localeSplit[1] = !!localeSplit[1] ? localeSplit[1].toUpperCase() : '';
					return localeSplit.join('_');
				},

				bccCapture: function (campaignId, cellNumber, referrerId) {
					try {
						$rootScope.isTargetBcusEligible();
						var url = window.location.href;
						var email = "";
						var firstName = "";
						var lastName = "";
						var dayPhone = "";
						var evePhone = "";
						var haMilesNumber = "";
						var paymentFormIPS = 1;//if user is logged-in and if it's payment page then don't trigger post message "barclaysRequest"
console.log('returl', url);
						if (url.toLowerCase().indexOf("/book/pax/index#/passenger/1") !== -1) {
							if ($("input[name=firstname]").length) firstName = $("input[name=firstname]").val();
							if ($("input[name=lastname]").length) lastName = $("input[name=lastname]").val();

							if ($("input[name='Member1']:checked").val()) {
								if ($("input[name=partnerairline]").length) {
									var partner = $("input[name=partnerairline]").val();
									if (partner.indexOf("HA") !== -1) {
										if ($("input[name=hmnumber]").length) {
											haMilesNumber = $("input[name=hmnumber]").val();
										}
									}
								}
							} else {
								if ($('input[name=email]').length) email = $("input[name=email]").val();
							}
						} else if (url.toLowerCase().indexOf("/book/pax/index#/contact") !== -1) {
							if ($("input[name=email]").length) email = $("input[name=email]").val();
							if ($("input[name=PhoneNum0]").length) dayPhone = $("input[name=PhoneNum0]").val();
							if ($("input[name=PhoneNum1]").length) evePhone = $("input[name=PhoneNum1]").val();
						}

						var json = {
							"cellNumber": cellNumber,
							"campaignId": campaignId,
							"referrerId": referrerId,
							"returl": url,
							"first": firstName,
							"last": lastName,
							"email": email,
							"dayPhone": dayPhone,
							"evePhone": evePhone,
							"HaMilesNumber": haMilesNumber
						};
						if (window.location.hostname.indexOf('webview') !== -1) {
							Promise.all([
								$scs.get('BarclaysPopupContent.Heading'),
								$scs.get('BarclaysPopupContent.Content'),
								$scs.get('BarclaysPopupContent.Title'),
								$scs.get('BarclaysPopupContent.OkayButtonText'),
								$scs.get('BarclaysPopupContent.CancelButtonText')
							])
								.then(function (strings) {
									haModal(haConfig.getTemplateUrl('ha-nma-modal.html'), {
										id: "barclays-modal",
										backdrop: 'true',
										heading: strings[0],
										title: strings[2],
										success: {
											label: strings[3], fn:
												function () {
													$.when(bccTargetApply()).done(function (cc) {
														// if on payment page, form data is in an iframe, so we'll utilize existing code
														if (url.toLowerCase().indexOf("/book/payment") !== -1 && paymentFormIPS == 1) {
															sessionStorage.setItem("referrerId", referrerId);
															var iframe = document.getElementById("formIFrame");
															iframe.contentWindow.postMessage("barclaysRequest", iframeOrigin);
															$rootScope.isTargetBcusEligible();
														}
													});
												}
										},
										cancel: {
											label: strings[4]
										},
										size: 'modal-md',
										defaultContent: strings[1]
									});
								});
						}
						else
						{
						    $.when(bccTargetApply()).done(function (cc) {
						        // if on payment page, form data is in an iframe, so we'll utilize existing code
								if (url.toLowerCase().indexOf("/book/payment") !== -1 && paymentFormIPS == 1) {
						            sessionStorage.setItem("referrerId", referrerId);
						            var iframe = document.getElementById("formIFrame");
						            iframe.contentWindow.postMessage("barclaysRequest", iframeOrigin);
						            $rootScope.isTargetBcusEligible();
						        }
						    });
						}

						function bccTargetApply() {
							return $.ajax({
								cache: false,
								async: false,
								type: "POST",
								dataType: "json",
								contentType: "application/json; charset=utf-8",
								url: window.location.origin + "/api/v2/target/BccApply/",
								data: JSON.stringify(json),
								success: function (data) {
									if (data.length) { window.location.href = data; paymentFormIPS = 0 }
								},
								error: function (abc, error) {
									$rootScope.isTargetBcusEligible();
									console.log(JSON.stringify(abc));
									return;
								}
							});
						}
					} catch (e) {
						console.log(e.message);
					}
				},

				isTargetBcusEligible: function () {
					return $.ajax({
						cache: false,
						async: false,
						type: "GET",
						timeout: 500,
						url: window.location.origin + "/api/v2/target/BccTargetEligibility/",
						success: function (data) {
							if (data === true) {
								$rootScope.IsBCusEligible = true;
							} else {
								$rootScope.IsBCusEligible = false;
							}
							return;
						},
						error: function (abc, error) {
							console.log(JSON.stringify(abc));
							console.log(error);
							$rootScope.IsBCusEligible = false;
						}
					});
				},

				isUpliftConfirmed: function () {
					return $.ajax({
						cache: false,
						async: false,
						type: "POST",
						timeout: 500,
						url: window.location.origin + "/Book/Payment/UpliftConfirm/",
						success: function (data) {
							if (data.OrderId !== "empty") {
								$rootScope.UpliftOrderId = data.OrderId;
								$rootScope.UpliftReservationCode = data.PNR;
								$rootScope.isUpliftConfirmed = true;
							} else {
								$rootScope.isUpliftConfirmed = false;
							}
							return;
						},
						error: function (abc, error) {
							console.log(JSON.stringify(abc));
							console.log(error);
							$rootScope.isUpliftConfirmed = false;
						}
					});
				}

		    };

		    var urlParams;

		    function parseQS() {
		        var match;
		        var amp = /([^&=]+)=?([^&]*)/g;
		        var query = window.location.search.substring(1);
		        urlParams = {};

		        while ((match = amp.exec(query))) {
		            urlParams[decode(match[1])] = decode(match[2]);
		        }
		    }

		    function decode(s) {
		        return decodeURIComponent(s.replace(/\+/g, ' '));// replace addition symbol with a space
		    }

		    function parseFSModel(item) {
		        var fsModel = null;
		        var valueList = item.split('|');
		        if (valueList.length === 6) {
		            fsModel = {
		                FlightSearchSegmentList: [],
		                FlightQueryTypeId: parseInt(valueList[1]),
		                AdultCount: parseInt(valueList[2]),
		                ChildCount: parseInt(valueList[3]),
		                InfantCount: parseInt(valueList[4]),
		                IsRefundable: (valueList[5] === 'true') ? true : false
		            };

		            var segmentList = valueList[0].split('+');

		            angular.forEach(segmentList, function (segment) {
		                var partList = segment.split('=');
		                if (partList.length === 4) {
		                    fsModel.FlightSearchSegmentList.push({
		                        DepartureDate: partList[0],
		                        OriginCityCode: partList[1],
		                        DestinationCityCode: partList[2],
		                        IsMiles: (partList[3] === 'true') ? true : false
		                    });
		                }
		            });
		        }

		        return fsModel;
		    }

		    //$(window).on('onpopstate', parseQS); maybe needed someday?

		    // Inject language checks into $rootScope...
		    $rootScope.constructor.prototype.isEN = svc.isEN;
		    $rootScope.constructor.prototype.isJP = svc.isJP;
		    $rootScope.constructor.prototype.isKR = svc.isKR;
		    $rootScope.constructor.prototype.isCN = svc.isCN;
		    $rootScope.constructor.prototype.isTW = svc.isTW;

		    return svc;
		}
    ]);

})(angular);
;
(function (angular) {

	'use strict';

	var module = angular.module('haConfigModule', []);

	module.factory('haConfig', [function () {

		var templateRoot = '/templates/';
		var sitecoreRoot = '/sitecoreresources/';
		var languageCode = 'en-US';
		var tlds = [{'com': 'en'}, {'co.jp': 'ja-jp'}, {'co.kr': 'ko-kr'}, {'com.cn': 'zh-cn'}, {'com.tw': 'zh-tw'}, {'com.au': 'en-au'}, {'co.nz': 'en-nz'}];
		var hostArr = location.host.split('.');
		var tld = hostArr.length < 4 || !isNaN(parseInt(hostArr[0], 10)) ? 'com' : hostArr[2] + '.' + hostArr[3];
		var cultureIdx = tlds.map(function (i) { return typeof i[tld] !== 'undefined'; }).indexOf(true);
		var langString = (HA.cdn.html && HA.cdn.js && HA.cdn.css && HA.cdn.img) ? '?sc_lang=' + tlds[cultureIdx][tld] : '';
		var cacheVer = window.ver;

		if (HA.cdn.isOldIE) {
			HA.cdn.html = HA.cdnDynamic = '';	// JS, CSS, IMG are ok cross domain
		}

		return {

			/*
				The following helper methods are to facilitate getting the CDN prefix added to urls.
				For now they all assume you're passing in a path and query and not a fully qualified url
				as passing in a full url defeats the point of helper methods to prepend a CDN prefix.

				Should you pass in a full url, these methods will echo back your parameter as there is no way
				for them to discern your intentions.
			*/

			// TEMPLATES
			getTemplateUrl: function (url) {
				return url ? [HA.cdn.html, templateRoot, url.replace(/^\/|\/$/g, ''), '?ver=', cacheVer].join('') : '';
			},
			getTemplateUrlWithInclude: function (url) {
				return '<div ng-include="\'' + this.getTemplateUrl(url) + '\'"></div>';
			},
			getRazorTemplateUrl: function (url) {
				url += langString;
				var urlAppend = (~url.indexOf('?') ? '&' : '?') + 'ver=' + cacheVer;
				return url ? [HA.cdn.html, templateRoot, url.replace(/^\/|\/$/g, ''), urlAppend].join('') : '';
			},

			// IMAGES
			getImgUrl: function (pathAndQuery) {

				// If a full url was passed echo it back
				if (/^http/i.test(pathAndQuery) || /^\/\//i.test(pathAndQuery)) {
					return pathAndQuery;
				}

				// If there is a query string append sc_lang with an & instead of a ?
				var querySafeLangString = langString;
				if (/\?/gi.test(pathAndQuery) && langString) {
					querySafeLangString = '&' + langString.substring(1);
				}

				return pathAndQuery ? [HA.cdn.img, '/', pathAndQuery.replace(/^\/|\/$/g, ''), querySafeLangString].join('') : '';
			},

			// JSON ENDPOINTS
			getSitecoreResourceUrl: function (url) {
				return url ? [HA.cdnDynamic, sitecoreRoot, url.replace(/^\/|\/$/g, ''), langString].join('') : '';
			},
			getDynamicJsonUrl: function (pathAndQuery) {

				// If a full url was passed echo it back
				if (/^http/i.test(pathAndQuery) || /^\/\//i.test(pathAndQuery)) {
					return pathAndQuery;
				}

				// If there is a query string append sc_lang with an & instead of a ?
				var querySafeLangString = langString;
				if (/\?/gi.test(pathAndQuery) && langString) {
					querySafeLangString = '&' + langString.substring(1);
				}

				return pathAndQuery ? [HA.cdnDynamic, '/', pathAndQuery.replace(/^\/|\/$/g, ''), querySafeLangString].join('') : '';
			},



			// Language code helpers
			setLanguageCode: function (lc) {
				languageCode = lc;
				if (lc === 'en') {
					languageCode = 'en-US';
				}
			},
			getLanguageCode: function () {
				return languageCode;
			}
		};
	}
	]);

})(angular);
;
(function (angular) {

	// Hawaiian Feature Flags
	// ======================
	//
	// * **Class:** haFeatureFlags
	// * **Author:** Nathan Probst
	//
	// A utility class to access Feature Flags declared globally without declaring globals in code.

	'use strict';

	var module = angular.module('haFeatureFlagsModule', []);

	module.provider('haFeatureFlags', ['$windowProvider', function ($windowProvider) {
		var $window = $windowProvider.$get();
		var FLAGS = (($window.HA != null) && $window.HA.FLAGS) || {};
		var GLOBAL_DEFAULTS = {};

		var hasDefault = this.hasDefault = function (name) {
			return (typeof GLOBAL_DEFAULTS[name] !== 'undefined');
		};

		var has = this.has = function (name) {
			return (typeof FLAGS[name] !== 'undefined');
		};

		var get = this.get = function (name, localDefault) {
			if (has(name)) {
				return FLAGS[name];
			} else if (typeof localDefault !== 'undefined') {
				return localDefault;
			} else if (hasDefault(name)) {
				return GLOBAL_DEFAULTS[name];
			}
			return undefined;
		};

		this.setDefault = function (name, def) {
			GLOBAL_DEFAULTS[name] = def;
		};

		this.getDefault = function (name) {
			return GLOBAL_DEFAULTS[name];
		};

		this.$get = function () {
			return {
				hasDefault: hasDefault,
				has: has,
				get: get,
				setDefault: this.setDefault,
				getDefault: this.getDefault
			};
		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	var module = angular.module('haDateUtilsModule', []);

	module.factory('haDateUtils', ['$locale', function ($locale) {

		Date.prototype.dateAdd = function (size, value) {
			value = parseInt(value);
			var incr = 0;
			switch (size) {
				case 'day':
					incr = value * 24;
					this.dateAdd('hour', incr);
					break;
				case 'hour':
					incr = value * 60;
					this.dateAdd('minute', incr);
					break;
				case 'week':
					incr = value * 7;
					this.dateAdd('day', incr);
					break;
				case 'minute':
					incr = value * 60;
					this.dateAdd('second', incr);
					break;
				case 'second':
					incr = value * 1000;
					this.dateAdd('millisecond', incr);
					break;
				case 'month':
					value = value + this.getUTCMonth();
					if (value / 12 > 0) {
						this.dateAdd('year', value / 12);
						value = value % 12;
					}
					this.setUTCMonth(value);
					break;
				case 'millisecond':
					this.setTime(this.getTime() + value);
					break;
				case 'year':
					this.setFullYear(this.getUTCFullYear() + value);
					break;
				default:
					throw new Error('Invalid date increment passed');
			}

			return this;
		};
		Date.prototype.YYYY_MM_DD = function () {
			return this.getFullYear() + pad(this.getMonth() + 1) + pad(this.getDate());
		};
		function pad(n) {
			return n > 9 ? '-' + n : '-0' + n;
		}

		return {
			getVisibleMonths: function (monthsBack, monthsForward) {
				var startDate = (monthsBack > 0) ? (new Date()).dateAdd('month', monthsBack * -1) : new Date();
				var currentMonth = startDate.getMonth();
				var currentYear = startDate.getFullYear();
				var months = [];
				//monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
				var monthNames = $locale.DATETIME_FORMATS.MONTH;

				for (var i = 0; i < (monthsBack + monthsForward + 1); i++) {

					if (currentMonth === 12) {
						currentMonth = 0;
						currentYear++;
					}

					months.push({
						weeks: this.getVisibleWeeks(new Date(currentYear, currentMonth, 1)),
						name: monthNames[currentMonth] + ' ' + currentYear
					});

					currentYear = (currentMonth > 11) ? currentYear + 1 : currentYear;
					currentMonth = (currentMonth > 11) ? currentMonth - 12 : ++currentMonth;
				}

				return months;
			},
			getVisibleWeeks: function (date) {
				date = new Date(date || new Date());
				date.setDate(1);
				date.setHours(0);
				date.setMinutes(0);
				date.setSeconds(0);
				date.setMilliseconds(0);

				//fixes rare edge case of 1st day of week and 1st day of month
				if (!(date.getDay() === 0 && date.getDate() === 1)) {
					if (date.getDay() === 0) {
						date.setDate(-5);
					} else {
						date.setDate(date.getDate() - (date.getDay()));
					}
					if (date.getDate() === 1) {
						date.setDate(-6);
					}
				}

				var weeks = [];
				while (weeks.length < 6) {
					/*jshint -W116 */
					//if(date.getYear()=== startYear && date.getMonth() > startMonth) break;
					var week = [];
					for (var i = 0; i < 7; i++) {
						week.push(new Date(date));
						date.setDate(date.getDate() + 1);
					}
					weeks.push(week);
				}
				return weeks;
			},
			getDaysOfWeek: function (date) {
				date = new Date(date || new Date());
				date = new Date(date.getFullYear(), date.getMonth(), date.getDate());
				date.setDate(date.getDate() - (date.getDay()));
				var days = [];
				for (var i = 0; i < 7; i++) {
					days.push(new Date(date));
					date.setDate(date.getDate() + 1);
				}
				return days;
			},
			isValid: function (year, month, day) {
				var yearNum = parseInt(year, 10);
				var monthNum = parseInt(month, 10);
				var dayNum = parseInt(day, 10);

				if (!yearNum || isNaN(monthNum) || !dayNum) {
					return false;
				}
				var date = new Date(yearNum, monthNum - 1, dayNum);

				return date.getFullYear() === yearNum && date.getMonth() === monthNum - 1 && date.getDate() === dayNum;
			},
			isBetween: function (checkDate, date1, date2) {
				return this.isAfter(checkDate, date1) && this.isBefore(checkDate, date2);
			},
			isAfter: function (date1, date2) {
				if (!angular.isDate(date1) || !angular.isDate(date2)) {
					return false;
				}
				return date1.getTime() > date2.getTime();
			},
			isBefore: function (date1, date2) {
				if (!angular.isDate(date1) || !angular.isDate(date2)) {
					return false;
				}
				return date1.getTime() < date2.getTime();
			},
			isSameYear: function (date1, date2) {
				if (!angular.isDate(date1) || !angular.isDate(date2)) {
					return false;
				}
				return date1.getFullYear() === date2.getFullYear();
			},
			isSameMonth: function (date1, date2) {
				if (!angular.isDate(date1) || !angular.isDate(date2)) {
					return false;
				}
				return this.isSameYear(date1, date2) && date1.getMonth() === date2.getMonth();
			},
			isSameDay: function (date1, date2) {
				if (!angular.isDate(date1) || !angular.isDate(date2)) {
					return false;
				}
				return this.isSameMonth(date1, date2) && date1.getDate() === date2.getDate();
			},
			isAfter331: function (date1, date2) {
				if (!angular.isDate(date1) || !angular.isDate(date2)) {
					return false;
				}
				var oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
				var diffDays = Math.round(Math.abs((date1.getTime() - date2.getTime()) / (oneDay)));
				return (diffDays >= 330);
			},
			msToDate: function (ms) {
				return new Date(ms);
			},
			isNextDay: function (date1, date2) {
				if (!angular.isDate(date1) || !angular.isDate(date2)) {
					return false;
				}
				return date1.getTime() < date2.getTime();
			},
			numDaysDifference: function (date1, date2) {
				if (!angular.isDate(date1) || !angular.isDate(date2)) {
					return false;
				}
				var oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
				return Math.round(Math.abs((date1.getTime() - date2.getTime()) / (oneDay)));
			}
		};
	}]);

})(angular);
;
(function (angular) {

	// Hawaiian Sitecore Strings Service
	// =================================
	//
	// * **Class:** haSitecoreStrings
	// * **Author:** Nathan Probst
	//
	// Access strings from Sitecore Resources.

	'use strict';

	// var module = angular.module('haViewModelModule', []);
	var module;
	try {
		module = angular.module('haViewModelModule');
	} catch (e) {
		module = angular.module('haViewModelModule', []);
	}

	module.factory('haViewModelSvc', [
		'$log',
		'$q',

		function ($log, $q) {

			var viewModels = {};
			var deferreds = {};

			var defer = function (vmName, deferred) {
				$log.debug('_defer', vmName);
				var d = deferreds[vmName] = deferreds[vmName] || [];
				d.push(deferred);
			};

			var resolve = function (vmName) {
				if (viewModels[vmName] == null) {
					return;
				}

				var ds = deferreds[vmName] || [];
				while (ds.length > 0) {
					$log.debug('_resolve', vmName);
					var d = ds.pop();
					d.resolve(viewModels[vmName]);
				}
			};

			var put = function (vmName, data) {
				viewModels[vmName] = data;
				resolve(vmName);
			};

			var get = function (vmName) {
				var d = $q.defer();

				if (viewModels[vmName] != null) {
					d.resolve(viewModels[vmName]);
				} else {
					defer(vmName, d);
				}

				return d.promise;
			};

			var getSync = function (vmName) {
				return viewModels[vmName];
			};

			return {
				put: put,
				get: get,
				getSync: getSync
			};
		}
	]);

})(angular);
;
(function (angular) {

	// Hawaiian RegExp Service
	// =================================
	//
	// * **Class:** haRegexService
	// * **Author:** Jamie Perkins
	//
	// Centralized regexes

	'use strict';

	var module = angular.module('haRegexModule', []);

	module.factory('haRegexService', function () {

	    var pnr = /^[a-zA-Z0-9]{6}$/;
		var confirmationCode = /^[0-9]{13}$/;
		var owwConfCode = /^(((PB|pb|Pb|pB)[a-zA-Z0-9]{13,13})|\d{12,14})$/;
		var hmNumber = /^(\d){9}$/;
		var email = /^[\w\d\.\-]+@[a-zA-Z\d\.\-]+\.[a-zA-Z]{2,15}$/; // test here: http://regexr.com/3c0b8
		var username = /^(?=.*[a-zA-Z])([a-zA-Z0-9\.]{6,30})$/;
		var regexObj = {
		    alphaNumeric: /[a-zA-Z0-9- ]/gi,
		    hmNumber: hmNumber,
		    email: email,
		    phone: /[0-9]{10,15}/,
		    name: /^[a-zA-Z-\s]{0,30}$/,
			addressLine: /^[a-zA-Z0-9-\.\,\/\\ ]{0,29}$/,
		    city: /^[a-zA-Z-\s]{0,30}$/,
		    state: /^[a-zA-Z0-9- ]{0,30}$/,
		    username: username,
		    login: new RegExp(hmNumber.source + '|' + email.source + '|' + username.source),
		    password: /^(?=.*[a-z])(?=.*\d)(?=.*[A-Z]).{10,16}$/, // http://regexr.com/3c65b
		    usernameOrHmNumber: new RegExp(hmNumber.source + '|' + username.source),
		    creditCard: /^[0-9]{15,16}$/, // note that ha-validate-card-type does the heavy lifting
		    redress: /^[a-zA-Z0-9]{0,13}$/, // as per PO updated the minimum character restriction
		    knownTraveler: /^[a-zA-Z0-9]{0,25}$/, // as per PO updated the minimum character restriction		    
		    jalFlightNumber: /(^(JL|NU)?[0-9]{1,4}$)|^[0-9]{1,4}$/i,//Jal and JTA specific flight num validation
		    flightStatusByNumber: /^(ha|HA)?[0-9]{1,4}$/,
		    pnr: pnr,
		    confirmationCode: confirmationCode,
		    owwConfCode: owwConfCode,
		    pnrOrConfCode: new RegExp(pnr.source + '|' + confirmationCode.source),
		    pnrOrConfOrOwwConfCode: new RegExp(pnr.source + '|' + confirmationCode.source + '|' + owwConfCode.source),
		    maikaiNumber: /^[0-9]{12}/, // also validated in ha-foodland-registration.js
		    agentCode: /^[a-zA-Z0-9]{1,10}$/,
		    giftcardnumber: /\d{19}/,
		    giftcardpin: /\d{4}/,
		    chinaRewardMemberShipNumber: /^(cr|CR)\d{16}$/,
		    ticketOrMSR: /^\d{13}$/,
		    cashbagNum: /^\d{16}$/,
			promoCode: /^[a-zA-Z0-9_-]{1,15}$/,
			carMembershipNumber: /^[a-zA-Z0-9]{6}$/,
			carDiscountNumber: /^[a-zA-Z]{1}[0-9]{6}$/,
			carCouponNumber: /^[a-zA-Z]{4}[0-9]{3}$/,
			flightNumber: /^[0-9]{1,5}$/
		};

		return regexObj;
	});

	module.run([
		'$rootScope',
		'haRegexService',

		// attach regexes to root scope
		function ($rootScope, svc) {
			$rootScope.$regex = svc;
		}
	]);

})(angular);
;
(function (angular) {

	// Ha AriaLive Service
	// --------------------------------------------
	//
	// * **Class:** haAriaLiveService
	// * **Author:** Cory Shaw
	//
	// Service for updating and destroying the content of the aira-live element for screen readers


	'use strict';

	var mod = angular.module('haAriaLiveModule', []);

	mod.service('haAriaLiveService', ['$rootScope', '$timeout', '$log', function ($rootScope, $timeout) {

		var service = {

			updateMessage: function (message) {
				// $log.log('AriaLive Service Update Message called');
				$rootScope.ariaLiveMessage = message;
				// destroy the message after 2 seconds so that if it's updated with the same information again, the screen reader will read it off again.
				$timeout(function () {
					$rootScope.ariaLiveMessage = '';
				}, 5000);
			}

		};

		return service;
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	var module;
	try {
		module = angular.module('haMobileService');
	} catch (e) {
		module = angular.module('haMobileService', []);
	}

	module.service('haMobileSvc', [

		function () {

			var mobilebreakpoint = 767;

			this.determineIsMobile = function() {
                if (window.innerWidth > mobilebreakpoint) {
					window.isMobile = false;
                    return false;
                } else {
					window.isMobile = true;
                    return true;
                }
            };

		}
	]);

	/*
	 forces a nested column to span the width of the viewport, ONLY on mobile
	 */
	module.directive('haFlushFittingCol',  ['$rootScope', function ($rootScope) {

		return {
			restrict: 'A',
			link: function ($scope, $el, $attrs) {

				if ($rootScope.isMobile) {

					var negativeMargin = 0 - $el.offset().left;

					$el.css({
						marginLeft: negativeMargin,
						marginRight: negativeMargin,
						width: 'auto',
						overflow: 'hidden'
					});

					if ( $attrs.preservepadding !== "true" ) {
						$el.css({
							padding: 0
						});
					}
				}
			}
		}
	}]);

	/*
	 makes all columns in a row equal heights, ONLY on mobile. Requires at least 50px height
	 */
	module.directive('haEqualHeightColumns',  ['$rootScope', '$interval', function ($rootScope, $interval) {

		return {
			restrict: 'A',
			link: function ($scope, $el) {

				if ($rootScope.isMobile) {
					var highest = 0,
						minRequiredHeight = 50;
					// wait for render
					var ready = $interval(function() {
						if ($el.children(':last').outerHeight() > minRequiredHeight) {
							$interval.cancel(ready);
							$el.children().each(function() {
								if ($(this).outerHeight() > highest) {
									highest = $(this).outerHeight();
								}
							});
							$el.children().each(function() {
								if ($(this).outerHeight() < highest) {
									$(this).css('height', highest);
								}
							});
						}
					}, 50);
				}
			}
		}
	}]);

})(angular);
;
// Hawaiian Geo Data Service
// =========================
//
// * **Class:** haGeoDataSvc
// * **Author:** Nathan Probst
//
// Service for accessing geo data and logic

(function (angular) {
	'use strict';

	var module;
	try {
		module = angular.module('haGeoDataModule');
	} catch (e) {
		module = angular.module('haGeoDataModule', ['haHttpService']);
	}

	module.factory('haGeoDataSvc', [
		'$q',
		'$log',
		'$filter',
		'$cacheFactory',
		'haGeoDataAPI',

		function ($q, $log, $filter, $cacheFactory, api) {
			var cache = $cacheFactory('haGeoDataCache');
			var regexCache = $cacheFactory('regexCache');
			var activeCountry = null;
			var COUNTRY = 'COUNTRY';
			var STATES = 'STATES_';
			var CITIES = 'CITIES_';
			var COUNTRY_PHONE_CODES = 'COUNTRY_PHONE_CODES';

			var svc = {
				setCountryData: function (data) {
					data = $filter('orderBy')(data, ['Sorting', 'DisplayName']);
					return cache.put(COUNTRY, data);
				},
				setActiveCountry: function (country) {
					activeCountry = country;
				},
				getCountries: function () {
					var KEY = COUNTRY;
					var cached = cache.get(KEY);
					if (cached != null && cached.length > 0) {
						var d = $q.defer();
						d.resolve(cached);
						return d.promise;
					} else {
						return api.getCountries().then(function (data) {
							data = $filter('orderBy')(data, 'Sorting');
							return cache.put(KEY, data);
						});
					}
				},
				getStates: function (countryKey) {
					var KEY = STATES + countryKey;
					var cached = cache.get(KEY);
					if (cached != null) {
						var d = $q.defer();
						d.resolve(cached);
						return d.promise;
					} else {
						return api.getStates(countryKey).then(function (data) {
							return cache.put(KEY, data);
						});
					}
				},
				getStateAndCities: function (countryKey, postalCode) {
					var KEY = CITIES + countryKey + postalCode;
					var cached = cache.get(KEY);
					if (cached != null) {
						var d = $q.defer();
						d.resolve(cached);
						return d.promise;
					} else {
						return api.getStateAndCities(countryKey, postalCode).then(function (data) {
							return cache.put(KEY, data);
						});
					}
				},
				getAddressStyle: function (countryKey) {
					countryKey = countryKey || (activeCountry && activeCountry.Key);
					var cached = cache.get(COUNTRY);
					if (cached != null) {
						for (var i = 0; i < cached.length; i++) {
							var c = cached[i];
							if (c != null && c.Key === countryKey) {
								return c.AddressStyle || c.Iso2Code;
							}
						}
					}
					return null;
				},
				noStates: function (countryKey) {
					countryKey = countryKey || (activeCountry && activeCountry.Key);
					var cached = cache.get(COUNTRY);
					if (cached != null) {
						for (var i = 0; i < cached.length; i++) {
							var c = cached[i];
							if (c != null && c.Key === countryKey) {
								if (c.StateValidation === 'X') {
									return true;
								}
								break;
							}
						}
					}
					return false;
				},
				hasPostalCode: function (countryKey) {
					countryKey = countryKey || (activeCountry && activeCountry.Key);
					var cached = cache.get(COUNTRY);
					if (cached != null) {
						for (var i = 0; i < cached.length; i++) {
							var c = cached[i];
							if (c != null && c.Key === countryKey) {
								if (c.PostalCodeValidation === 'X') {
									return false;
								}
								break;
							}
						}
					}
					return true;
				},
				getPostalCodeRegex: function (countryKey) {
					countryKey = countryKey || (activeCountry && activeCountry.Key);

					// check the regex cache for this countryKey
					var re = regexCache.get(countryKey) || regexCache.put(countryKey, {});
					if (re.PostalCodeValidation) {
						return re.PostalCodeValidation;
					}

					var cached = cache.get(COUNTRY);
					if (cached != null) {
						for (var i = 0; i < cached.length; i++) {
							var c = cached[i];
							if (c != null && c.Key === countryKey) {
								if (c.PostalCodeValidation != null && c.PostalCodeValidation !== 'X') {
									// cache the generated regex to prevent infinite digest loops with ng-pattern
									re.PostalCodeValidation = new RegExp(c.PostalCodeValidation);
									return re.PostalCodeValidation;
								}
								break;
							}
						}
					}
					re.PostalCodeValidation = /.*/;
					return re.PostalCodeValidation;    // Match anything
				},
				getPhoneNumberRegex: function (countryKey) {
					countryKey = countryKey || (activeCountry && activeCountry.Key);

					// check the regex cache for this countryKey
					var re = regexCache.get(countryKey) || regexCache.put(countryKey, {});
					if (re.PhoneNumberValidation) {
						return re.PhoneNumberValidation;
					}

					var cached = cache.get(COUNTRY);
					if (cached != null) {
						for (var i = 0; i < cached.length; i++) {
							var c = cached[i];
							if (c != null && c.Key === countryKey) {
								if (c.PhoneNumberValidation != null) {
									// cache the generated regex to prevent infinite digest loops with ng-pattern
									re.PhoneNumberValidation = new RegExp(c.PhoneNumberValidation);
									return re.PhoneNumberValidation;
								}
								break;
							}
						}
					}
					re.PhoneNumberValidation = /.*/;
					return re.PhoneNumberValidation;    // Match anything
				},
				getCityRegex: function (countryKey) {
					countryKey = countryKey || (activeCountry && activeCountry.Key);

					// check the regex cache for this countryKey
					var re = regexCache.get(countryKey) || regexCache.put(countryKey, {});
					if (re.CityValidation) {
						return re.CityValidation;
					}

					var cached = cache.get(COUNTRY);
					if (cached != null) {
						for (var i = 0; i < cached.length; i++) {
							var c = cached[i];
							if (c != null && c.Key === countryKey) {
								if (c.CityValidation != null) {
									// cache the generated regex to prevent infinite digest loops with ng-pattern
									re.CityValidation = new RegExp(c.CityValidation);
									return re.CityValidation;
								}
								break;
							}
						}
					}
					re.CityValidation = /^[a-zA-Z- ]{0,30}$/;
					return re.CityValidation;   // Default
				},
				getPhoneCountryCodes: function () {
					var KEY = COUNTRY_PHONE_CODES;
					var cached = cache.get(KEY);
					if (cached != null && cached.length>0) {
						return cached;
					} else {
						var countries = cache.get(COUNTRY);
						var customFilter = function (country) {
							// USA > Tier 1 > Rest, All sorted by ISO Code
							return country.IsoCode === 'USA' ? 1 : (parseInt(country.CountryTier) === 1 ? 2 : 3);
						};
						countries = $filter('orderBy')(countries, [customFilter, 'IsoCode']);
						if (countries != null) {
							var data = [];
							for (var i = 0; i < countries.length; i++) {
								var code = countries[i].PhoneCountryCode;
								var iso = countries[i].IsoCode;
								var key = countries[i].Key;
								if (code > 0) {
									data.push({
										Code: code,
										Key: key,
										Name: '+' + code,
										Value: iso
									});
								}
							}
							return cache.put(KEY, data);
						} else {
							return [];
						}
					}
				},
				lookupCountryByCode: function (countryCode) {
					var cached = cache.get(COUNTRY);
					if (cached != null) {
						for (var i = 0; i < cached.length; i++) {
							var c = cached[i];
							if (c != null && c.IsoCode === countryCode) {
								return c;
							}
						}
					}
					return null;
				}
			};

			return svc;
		}
	]);

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haEncryptionModule', [])
		.factory('haEncryptionService', ['haEncryptionAPI', function (haEncryptionAPI) {
			return {
				Encrypt: function (dataToEncrypt, key) {
					if (validString(dataToEncrypt) && validString(key)) {
						return haEncryptionAPI.encryptString(dataToEncrypt, key);
					}

					return Promise.reject(formatError());
				},
				EncryptString: function (dataToEncrypt, key) {
					return this.Encrypt(dataToEncrypt, key).then(
						function (response) {
							return response.data;
						},
						function (error) {
							console.error("Encryption Error: "+error);
							return "";
					});

				}
			};

			function formatError() {
				return "Format of data is invalid in EncryptionService.";
			}

			function validString(data) {
				return (data && typeof (data) === 'string' && data !== '') ? true : false;
			}
		}
		]);
}
)(angular)
;
(function(angular) {
		
	'use strict';

	angular.module('haLaunchDarklyModule',[])
		.factory('haLaunchDarklyService', [ 'haLaunchDarklyAPI', "$q", function(halaunchDarklyAPI,$q) {
					
			return {
				GetFlagVariant: function (key, type, variant, useCache) {
					var validFormat = false;	
					switch (type) {
						case "boolean":
							validFormat = validBooleanVariant(variant);	
						break;

						case "integer":
							validFormat = validIntegerVariant(variant);
						break;

						case "float":
							validFormat = validFloatVariant(variant);
						break;

						case "dictionary":
							validFormat = validDictionaryVariant(variant)
						break;

						case "string":
							validFormat = validStringVariant(variant)
						break;
							default:
								return Promise.reject("Invalid type: "+type+" sent to LaunchDarklyService.");
					}

					if (validFormat) {
						return halaunchDarklyAPI.getFeatureFlag(key, type, variant.toString(), useCache);			
					} else {
						return Promise.reject(formatError(type, variant));
					}
							
						},
						trackResult: function(id) {
							halaunchDarklyAPI.trackResult(id);
						},
						GetFlagVariantAsBoolean: function(key, type, variant) {
							
							 return this.GetFlagVariant(key, type, variant).then(function (response) {
								 
									return response.data === "ENABLED" ? true : false;
								},
								function(error) {
									console.error("Launch Darkly Error: "+error+"	KEY: "+attributes.key);
									return false;						
								});
						}
					};

					function formatError(type, variant) {

						return "Format of variant "+variant+" is invalid for type "+type+" in LaunchDarklyService.";
					}

					function validBooleanVariant(variant) {
						return variant === true || variant === false ? true : false;
					}

					function validIntegerVariant(variant) {
						return typeof (variant) === "number" ? true : false;
					}

					function validFloatVariant(variant) {
						return typeof (variant) === "number" && variant.toString().includes(".") ? true : false;
					}

					function validStringVariant(variant) {
						return typeof (variant) === 'string' ? true : false;
					}

					// Dictionary variant format is {JPath}-{value}
					// Value will a simple string literal. Not  object string
					// JPath is a string for selecting a json value https://www.newtonsoft.com/json/help/html/SelectToken.htm
					function validDictionaryVariant(variant) {
						return typeof (variant) === 'string' &&
							variant.indexOf("-") !== variant.length - 1
							? true
							: false;
					}
				}
			]);
}
)(angular)
;
(function (angular) {
	'use strict';

	var mod = angular.module('haUplift', []);

	mod.service('haUplift', ['haHttpService', 'haLaunchDarklyAPI', 'haPaymentAPI', '$rootScope', '$q', 'haUtils', function (haHttpService, halaunchDarklyAPI, haPaymentAPI, $rootScope, $q, haUtils) {
		var isCheckout = false;
		var UpAPI = "";
		var UpCode = "";
		var upliftConfiguration =  window.upliftConfiguration;
		window.UpliftGlobals = window.UpliftGlobals || {};
		window.UpliftGlobals.isUpliftEnabled = false;

		$rootScope.isUpEnabled = false;

        //load Uplift
		function loadUpliftJS() {
			if (upliftConfiguration) {
				UpCode = upliftConfiguration.UpliftCode;
				UpAPI = upliftConfiguration.UpliftAPI;
				$rootScope.UpTimeout = upliftConfiguration.UpliftTimeout;
				$rootScope.isUpEnabled = upliftConfiguration.IsUpliftEnabled;
				$rootScope.UpliftComponents = upliftConfiguration.ComponentCollection;

				//Load uplift
				setTimeout(function () {
						; (function (u, p, l, i, f, t, b, j) { u['UpLiftPlatformObject'] = f; u[f] = u[f] || function () { (u[f].q = u[f].q || []).push(arguments) }, u[f].l = 1 * new Date(); b = p.createElement(l), j = p.getElementsByTagName(l)[0]; b.async = 1; b.src = i + '?id=' + t; j.parentNode.insertBefore(b, j); var o = window.location.host.match(/[w-]+.w{2,3}(:d+)?$/); if (o) o = o[0]; u[f]('create', t, o) })(window, document, 'script', '//cdn.uplift-platform.com/a/up.js', 'up', UpCode)
				}, $rootScope.UpTimeout);

            }
			else {
				$rootScope.isUpEnabled = false;
			}
		}

		function initPayMonthly() {
			try {
				if (isCheckout && document.getElementById("up-pay-monthly-container") != null) {
					//initialize uplift for checkout phase
					window.Uplift.Payments.init({
						apiKey: UpAPI,
						locale: "en-US",
						currency: "USD",
						checkout: true,                         // set to true for Checkout phase
						container: "#up-pay-monthly-container", // CSS selector for Uplift iframe
						onChange: myOnChangeCallback
					});
				}
				else {
					//initialize uplift for shopping phase
					window.Uplift.Payments.init({
						apiKey: UpAPI,
						locale: "en-US",
						currency: "USD"
					});
				}
			} catch (e) {}
		}

		function myOnChangeCallback(response) {
			var statusHandlers = {
				OFFER_AVAILABLE: function () {
					//hide error prompt
					$("#uplifterror_mobile").hide();
					$("#uplifterror_web").hide();
					$("#upliftSegment").show();
				},
				TOKEN_AVAILABLE: function () {
					Uplift.Payments.getToken();
				},
				TOKEN_RETRIEVED: function () {
					var token = response.token;
					$rootScope.UpToken = token;
					//call to cde and pass token

					//hide confirm button when uplift application is not done
					var confirmBooking = $("#confirmBooking");
					var confirmBookingAmount = $("#confirmBookingAmount");

					if (confirmBooking && confirmBookingAmount) {
						confirmBooking.show();
						confirmBookingAmount.show();
					}
					var CDEToken = token.card_token;
					var OrderId = window.sessionStorage['up_br'];
					$rootScope.UpliftOrderId = OrderId;
					//send CDE token to api to save token into session
					haPaymentAPI.setUpliftToken(CDEToken, OrderId).success(function () {
						console.log("Token stored");
					}).error(function () {
						document.write();
						console.log("Token not stored");
					});
				},
				OFFER_UNAVAILABLE: function () {
					if (window.UpliftGlobals.ChangeCallback) {
						window.UpliftGlobals.ChangeCallback('OFFER_UNAVAILABLE');
					}
					//show error prompt
					if ($rootScope.isMobile) {
						$("#uplifterror_mobile").show();
					} else {
						$("#uplifterror_web").show();
					}
					//hide iframe
					$("#up-pay-monthly-container").hide();
					//disable pay monthly selector
					$("#check-monthly").attr('disabled', true);
					$("#upliftSegment").hide();
				},
				SERVICE_UNAVAILABLE: function () {
					if (window.UpliftGlobals.ChangeCallback) {
						window.UpliftGlobals.ChangeCallback('SERVICE_UNAVAILABLE');
					}
					//show error prompt
					if ($rootScope.isMobile) {
						$("#uplifterror_mobile").show();
					} else {
						$("#uplifterror_web").show();
					}
					$("#up-pay-monthly-container").hide();
					$("#check-monthly").attr('disabled', true);
					$("#upliftSegment").hide();
				}
			};
			statusHandlers[response.status]();
		}

		//compile trip info
		var upliftTripInfo = [];
		function buildTripInfo() {
			var travelersData = [];
			//send actual travelers information if used for checkout
			if (isCheckout) {
				var travellers = upliftTripInfo.itenerary.Travellers;
				var passengers = upliftTripInfo.itenerary.PassengerTripSummary.Passengers;

				//get passenger date of birth
				for (var i = 0; i < travellers.length; i++) {
					var month = travellers[i].DOBMonth < 10 ? "0" + travellers[i].DOBMonth : travellers[i].DOBMonth;
					var day = travellers[i].DOBDay < 10 ? "0" +travellers[i].DOBDay : travellers[i].DOBDay;
					travelersData.push({
						id: i + 1,
						first_name: passengers[i].FirstName,
						last_name: passengers[i].LastName,
						date_of_birth: month+"/"+day+"/"+travellers[i].DOBYear
					});
				}
			} else {
				//use generic traveler information for shopping phase
				var traveler = [
					{
						id: upliftTripInfo.id,
						first_name: 'Xxxxx',
						last_name: 'Xxxxx',
						date_of_birth: '01/01/2001',
					}
				]
				travelersData = traveler;
			}

			var itineraryData = [{
				departure_apc: upliftTripInfo.Origin,
				arrival_apc: upliftTripInfo.Destination,
				departure_time: upliftTripInfo.DepartureDate,
				arrival_time: upliftTripInfo.ArrivalDate
			}]

			//add itinerary data for checkout page
			if (isCheckout) {
				itineraryData = upliftTripInfo.iteneraryTrips;
			}

			var air_reservations = [
				{
					origin: upliftTripInfo.Origin,
					destination: upliftTripInfo.Destination,
					trip_type: upliftTripInfo.tripType,
					itinerary: itineraryData,
				}
			];
			//add insurance to trip info only if insurance is selected
			if (upliftTripInfo.IsTripInsuranceSelected) {
				air_reservations = [
					{
						origin: upliftTripInfo.Origin,
						destination: upliftTripInfo.Destination,
						trip_type: upliftTripInfo.tripType,
						itinerary: itineraryData,
						insurance: [
							{
								types: ["cancellation"],
								price: upliftTripInfo.insuranceCost * 100 ,//send insurance cost in cents
								price_per_person: (upliftTripInfo.insuranceCost / travellers.length) * 100
							}
						]
					}
				];
			}

			return {
				order_amount: Math.trunc(upliftTripInfo.order_amount * 100), //send itinerary cost in cents
				travelers: travelersData,
				air_reservations: air_reservations
			}
		};

	    // Array of promises to resolve first
		var upliftPrerequisites = [];

		//Check LD and other settings
		var launchDarklyDeferred = $q.defer();
		upliftPrerequisites.push(launchDarklyDeferred.promise);

	    // Ensure that the uplift placeholder elements are present
		var emitterDeferred = $q.defer();
		upliftPrerequisites.push(emitterDeferred.promise);

		var upliftEmitterFired = false;
		$rootScope.$on('UpliftElementReady', function () {
		    if (upliftEmitterFired) {
		        return;
		    }
		    upliftEmitterFired = true;
		    isUpliftEnabled();
		    emitterDeferred.resolve();
		});

		function isUpliftEnabled() {
			var islegacyIE = /MSIE|Trident/.test(window.navigator.userAgent);

			//check if browser is compatible
			if (upliftConfiguration.IsUpliftEnabled && !islegacyIE) {
		        launchDarklyDeferred.resolve()
		    } else {
				launchDarklyDeferred.reject()
		    }
		}

	    //// Is the Adobe Target flag enabled?
		var targetFlagDeferred = $q.defer();
		upliftPrerequisites.push(targetFlagDeferred.promise);

		var targetWatcher = $rootScope.$watch('EnableUplift', function (nv) {
			if (nv === true) {
				targetFlagDeferred.resolve();
			} else if (nv === false) {
				targetFlagDeferred.reject();
			}
		});

	    // Wait until all required checks have passed before loading the uplift js file
		$q.all(upliftPrerequisites).then(function () {
			$rootScope.isUpEnabled = true;
			window.UpliftGlobals.isUpliftEnabled = true;
			loadUpliftJS();
		});

        // Find if a teaser shoud be displayed
		$rootScope.showUpliftTeaser = function (key) {
			if (upliftConfiguration  && upliftConfiguration.ComponentCollection) {
				return (upliftConfiguration.ComponentCollection[key] ? upliftConfiguration.ComponentCollection[key] : false);
			}
			else {
				return false;
			}
		};

	    //called to trigger initialize uplift then send trip info
		window.upReady = function () {
		    initPayMonthly();
		    var tripInfo = buildTripInfo();
		    // load Pay Monthly and submit trip info to uplift
			try {
				window.Uplift.Payments.load(tripInfo);
				$("#upliftTabSelector").show();
		    } catch (e) {}
		}

		return {
			//reinitialize uplift
			upReady: function () {
				//ensure uplift is initialized before calling upready
				if (UpAPI !== '' && UpCode !== '') {
					window.upReady();
				}
			},
			//used for loading additional monthy offers in the page
			refreshUplift: function () {
				//ensure uplift is initialized before calling upready
				if (UpAPI !== '' && UpCode !== '') {
					try {
						var tripInfo = buildTripInfo();
						window.Uplift.Payments.load(tripInfo);
						CheckUpliftComponentEnabled();
					} catch (e) {}
				}
			},

			// Prepare flight data for uplift from the results.
			loadUplift: function (results, trip) {
				upliftTripInfo.tripType = (trip == 1 ? "oneway" : "roundtrip");
				if (results.ActiveTab != null) {
					angular.forEach(results.ActiveTab.TripAndFareDetails, function (result, i) {
						upliftTripInfo.Origin = result.TripSlice.Origin;
						upliftTripInfo.DepartureDate = (moment(result.TripSlice.DepartureDate).format('YYYY/MM/DD').toString()).replaceAll('/', '');
						upliftTripInfo.Destination = result.TripSlice.Destination;
						upliftTripInfo.ArrivalDate = (moment(result.TripSlice.ArrivalDate).format('YYYY/MM/DD').toString()).replaceAll('/', '');
						var tripid = 1;
						angular.forEach(result.FareDetails, function (cabin) {
							if (cabin.IsAvailable == true) {
								angular.forEach(cabin.FareTypes, function (fareInfo) {
									angular.forEach(fareInfo.FareInfos, function (baseFare) {
										upliftTripInfo.order_amount = baseFare.FiledInFareInfo.BaseFare;
										upliftTripInfo.id = tripid++;
									});
								});
							}
						});
					});
				}
			},
			// Prepare flight data for itineray page and checkout page to load monthly offers
			selectUplift: function (results, trips, isCheckoutPage, itenerary) {
				isCheckout = isCheckoutPage;
				var inteneraryData = [];
				angular.forEach(trips.AvailGridTrips, function (result) {
					var origin = result.Origin;
					var destination = result.Destination;
					var departure = (moment(result.DepartureDate).format('YYYY/MM/DD').toString()).replaceAll('/', '');
					var arrival = (moment(result.ArrivalDate).format('YYYY/MM/DD').toString()).replaceAll('/', '');
					var fareClass = result.CabinName;
					var departureCity = result.OriginCityName;
					var arrivalCity = result.DestinationCityName;

					inteneraryData.push({
						departure_apc: origin,
						departure_city: departureCity,
						arrival_apc: destination,
						arrival_city: arrivalCity,
						departure_time: departure,
						arrival_time: arrival,
						fare_class: fareClass,
						carrier_code: "HA",
						ticket_type: "digital",
						reservation_type: "standard",
						airline: "Hawaiian Airlines"
					})
					upliftTripInfo.Origin = origin;
					upliftTripInfo.DepartureDate = departure;
					upliftTripInfo.Destination = destination;
					upliftTripInfo.ArrivalDate = arrival;
					upliftTripInfo.id = result.TripID;
				});
				upliftTripInfo.tripType = (trips.TripType == 1 ? "oneway" : "roundtrip");
				upliftTripInfo.order_amount = results;
				upliftTripInfo.trips = trips;
				if (isCheckoutPage && itenerary) {
					upliftTripInfo.itenerary = itenerary;
					upliftTripInfo.iteneraryTrips = inteneraryData;
					if (itenerary.TripSummary.TripInsurance) {
						upliftTripInfo.insuranceCost = itenerary.TripSummary.TripInsurance.InsuranceCost;
					}
				}
				//window.upReady();
			},
			//called when uplift payment option is selected on checkout
			selectPayMonthly: function (isTripInsuranceSelected, isValidate) {
				if (isTripInsuranceSelected) {
					upliftTripInfo.IsTripInsuranceSelected = isTripInsuranceSelected;
				} else {
					upliftTripInfo.IsTripInsuranceSelected = false;
				}
				if ($rootScope.isUpEnabled) {
					this.refreshUplift();
					window.Uplift.Payments.select();
				}  
			},
			deselectPayMonthly: function (paymentMethod, isTripInsuranceSelected) {
				if ($rootScope.isUpEnabled) {
					upliftTripInfo.IsTripInsuranceSelected = isTripInsuranceSelected;
					this.refreshUplift();
					window.Uplift.Payments.deselect(paymentMethod);
				}
			},
			//for confirmation page
			confirmPayMonthly: function (confirmationId) {
				loadUpliftJS();
				window.upReady = function () {
					isCheckout = true;
					initPayMonthly();
					window.Uplift.Payments.confirm(confirmationId);
				}
			},
			getUpliftConfig: function () {
				$.ajax({
					cache: false,
					async: true, // async: false hangs the page.
					type: "GET",
					// timeout: 1000,
					url: window.location.origin + "/book/Uplift/UpliftConfiguration",
					success: function (data) {
						upliftConfiguration = data;
						if (upliftConfiguration.IsUpliftEnabled) {
							loadUpliftJS();
 						}
					},
					error: function (abc, error) {
						console.log(JSON.stringify(abc));
						console.log(error);
					}
				});
			}
		}
	}]);
	mod.directive('haUpliftEmitter', ['$rootScope', function ($rootScope) {
		return {
			restrict: 'A',
			link: function (el) {
				$rootScope.$broadcast('UpliftElementReady', el);
			}
		}
	}]);
})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haCitiesAPI', ['haHttpService'])
		.service('haCitiesAPI', ['haHttpService', '$cacheFactory', 'haConfig', 'haUtils', '$rootScope', '$q', function (http, $cacheFactory, haConfig, haUtils, $rootScope, $q) {
			// TODO: Refactor this caching HTTP service into its own service
			var httpCache = $cacheFactory.get('$http');
			var citiesVersion = $scs('Cities_Version').versionnumber || '5.0.7';
			var carLocations;
			var carLocationsById;
			var api = {

				CITY_LIST_URL: haConfig.getDynamicJsonUrl(['/Book/City/', citiesVersion, '/GetCities'].join('')),
				getCityList: function () {
					return http.GET(api.CITY_LIST_URL, { cache: httpCache, timeout: 10 * 1000 })
						.then(function (response) {
							$rootScope.$broadcast('citiesavailable');
							return response.data.CityList.Cities;
						});
				},

				CITY_PAIRS_URL: haConfig.getDynamicJsonUrl(['/Book/City/', citiesVersion, '/GetValidCities'].join('')),
				getCityPairs: function () {
					return http.GET(api.CITY_PAIRS_URL, { cache: httpCache, timeout: 10 * 1000 })
						.then(function (response) {
							return response.data.CityList.Cities;
						});
				},

				EXPERT_BOOKING_LIST_URL: haConfig.getDynamicJsonUrl(['/Book/City/', citiesVersion, '/GetCitiesExpertBooking'].join('')),
				getCityListExpertBooking: function () {
					return http.GET(api.EXPERT_BOOKING_LIST_URL, { cache: httpCache, timeout: 10 * 1000 })
						.then(function (response) {
							return response.data.CityList.Cities;
						});
				},

				EXPERT_BOOKING_PAIRS_URL: haConfig.getDynamicJsonUrl(['/Book/City/', citiesVersion, '/GetValidCitiesExpertBooking'].join('')),
				getCityPairsExpertBooking: function () {
					return http.GET(api.EXPERT_BOOKING_PAIRS_URL, { cache: httpCache, timeout: 10 * 1000 })
						.then(function (response) {
							return response.data.CityList.Cities;
						});
				},

				NITP_MAP_URL: '/Content/assets/models/NitpMap.json',
				getNitpMap: function () {
					return http.GET(api.NITP_MAP_URL, { cache: httpCache, timeout: 10 * 1000 })
						.then(function (response) {
							return response.data;
						});
				},

				FILTERED_CITY_LIST_URL: function (index, isDepartCity) {
					return '/Book/City/' + citiesVersion + '/FilterCityList?index=' + index + '&isDepartCity=' + isDepartCity;
				},
				getFilteredCityList: function (index, isDepartCity) {
					return http.GET(api.FILTERED_CITY_LIST_URL(index, isDepartCity), { cache: httpCache, timeout: 10 * 1000 })
						.then(function (response) {
							return (response.data.CityList != null) && response.data.CityList.Cities || [];
						});
				},

				EXPEDIA_SUGGEST_URL: function (query) {
					var base = 'https://suggest.expedia.com/api/v4/typeahead/';
					var maxResults = 5;
					var clientId = 'HawaiianHotelWidget';
					var siteId = '70209';
					var locale = haUtils.getStandardLocale();
					locale = locale === 'en_US' || locale === 'ja_JP' || locale === 'en_AU' || locale === 'en_NZ' || locale === 'ko_KR' ? locale : 'en_US';

					var afterQuery = '?regiontype=1631&locale=' + locale
						+ '&lob=HOTELS&format=json&ab=7140.1%7C7949.1&features=ta_hierarchy&maxresults=' + maxResults
						+ '&client=' + clientId + '&siteid=' + siteId;

					return base + query + afterQuery;
				},
				getExpediaCityList: function (query) {
					return http.GET(api.EXPEDIA_SUGGEST_URL(query), { cache: httpCache, timeout: 10 * 1000 })
						.then(function (response) {
							return response.data.sr;
						})
						.catch(function (error) {
							return error;
						});
				},
				RENTAL_CAR_LOCATION_URL: haConfig.getDynamicJsonUrl('/api/v2/cars/locations'),
				searchCarLocations: function (query, countries) {
					return api.getAllCarLocations().then(function (locations) {
						var results = filterCarLocations(query, locations[countries[0]]);
						if (results.length < 15 && countries[1] && countries[0] !== countries[1]) {
							results = results.concat(filterCarLocations(query, locations[countries[1]]));
						}

						// Truncate the results to 15 items.
						results.length = Math.min(results.length, 15);

						return results;
					});
				},
				getAllCarLocations: function () {
					if (carLocations) {
						//if fetch is in-progress- use that promise
						if (carLocations.then) {
							return carLocations;
						}
						// data already fetched & cached
						return $q.resolve(carLocations);
					}

					//store the promise to serve as a flag indicating that the fetch is in-progress
					carLocations = http.GET(api.RENTAL_CAR_LOCATION_URL, { cache: httpCache, timeout: 600 * 1000 })
						.then(function (response) {
							//overwrite the promise with the real data
							//group the locations by country
							carLocations = _.groupBy(response.data, 'country');
							carLocationsById = _.indexBy(response.data, 'id');

							//create a property with the combined text values. Used for auto-suggest searching
							angular.forEach(Object.keys(carLocations), function(k) {
								angular.forEach(carLocations[k], function (s) {
									s.text = ['', s.name, s.postal, s.city, s.airport].join(' ').toLocaleLowerCase();
								});
							});
							return carLocations;
						})
						.catch(function (error) {
							return error;
						});
					return carLocations; // not really the carLocations data yet... it's the promise at this point.
				},
				getCarLocationById: function getCarLocationById(id) {
					return api.getAllCarLocations().then(function(){
						return carLocationsById[id];
					});
				}
			};

			function filterCarLocations(query, locations) {
				query = query.toLocaleLowerCase();

				if (!query) {
					return locations;
				}

				// Step 1) Filter full location list by what they have typed in
				var results = locations.filter(function (s) { return s.text.indexOf(query) > -1; });

				// Step 2) Sort by items that have the text they typed [at the beginning] of a word. (if text is in the middle, the result goes to the bottom of the list)
				results = results.sort(function (a, b) {
					return test(b.text, query) - test(a.text, query);
				});

				return results;
			}
			function test(txt, query) { return Number(txt.indexOf(' ' + query) > -1); }

			return api;
		}]);

})(angular);
;
(function (angular) {

	'use strict';

	var mod = angular.module('haEcertAPI', ['haHttpService']);

	mod.service('haEcertAPI', ['haHttpService', function (http) {
		return {
			getPassengerCredit: function (model) {
				return http.POST('/TravelCreditRedemption/GetPassengerCredit', model);
			},
			selectDuplicatePassenger: function (index) {
				return http.POST('/TravelCreditRedemption/SelectDuplicatePassenger', {
					index: index
				});
			},
			fetchPopup: function (name) {
				return http.GET('/TravelCreditRedemption/' + name);
			},
			isValidPromo: function (promoEligibilityRequest) {
				return http.POST('/Book/Home/PromotionValid', promoEligibilityRequest);
			},
			validateRedeemPromoCode: function (promoCode) {
			    return http.POST('/Book/ECert/ValidateRedeemPromoCode', {
                    OfferCode: promoCode
			    });
			},
		    removePromoCodeFromSession: function () {
		        return http.POST('/Book/ECert/RemovePromoCodeFromSession');
		    },
		    HandleAffiliates: function (promoCode) {
		        return http.POST('/Program/Affiliate/ValidateAffiliate', {
		            promoCode: promoCode
		    });
		}
		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haEmailAPI', ['haHttpService'])

	.service('haEmailAPI', ['haHttpService', function (http) {
		return {
			emailAvailabilityCheck: function (data, canceler) {
				return http.POST('/MyAccount/EmailAvailability/EmailAvailabilityCheck',
					{emailAddress: data},
					{timeout: canceler.promise}
				).then(function (response) {
					return response.data.IsSuccess ||
						(response.data.Message === 'Internal Processing Error');	// Don't block the user if we're broken.
				});
			}
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Flight Results Service
	// --------------------------------------------
	//
	// * **Class:** haFlightResultsAPI
	// * **Author:** Josh Nielsen & Nathan Probst
	//
	// Service for fetching flight results

	'use strict';

	var mod = angular.module('haFlightResultsAPI', ['haHttpService']);

	mod.service('haFlightResultsAPI', ['haHttpService', function (http) {
		return {

			SEARCH_FLIGHTS_URL: '/Book/FlightResults/SearchFlights',
			fetch: function (/* searchRequestKey */) {
				return http.POST(this.SEARCH_FLIGHTS_URL, {});
			},

			// temporary method that fetches an alternative tab, in the end the booking dates should just be changed and then use fetch
			SEARCH_FLIGHTS_ALTERNATE_DATE_URL: '/Book/FlightResults/SearchFlightsAlternateDate',
			fetchTab: function (TripID, SelectedDate /* , searchRequestKey */) {
				return http.POST(this.SEARCH_FLIGHTS_ALTERNATE_DATE_URL, {
					tripId: TripID,
					selectedDate: SelectedDate
				});
			},

			SEARCH_FLIGHTS_STEP_THROUGH_URL: '/Book/FlightResults/SearchFlightsStepThrough',
			SearchFlightsStepThrough: function (jdDataList, TripId, isEdit) {
				return http.POST(this.SEARCH_FLIGHTS_STEP_THROUGH_URL, {
					JDDataList: jdDataList.JDDataList,
					tripId: TripId,
					isEdit: isEdit
				});
			},

			// temporary method that fetches an alternative currency result set, in the end the booking currency should just be changed and then use fetch
			fetchCurrency: function () {
				return http.GET('/scripts/modules/BOS_BKK_miles.json');
			},

			bookFlights: function (selectedfares, tripsummary) {
				return http.POST('/Book/FlightResults/ContinueBooking', {
					selectedFares: selectedfares,
					tripSummary: tripsummary
				});
			},

			// Method that fetch Modal content from sitecore for the passed modal name
			fetchModalContent: function (modalName) {
				return http.GET('/Book/FlightResults/ModalContent', {
					params: {
						modalName: modalName
					}
				});
			},

			checkFlightAvailability: function (selectedfares) {
				return http.POST('/Book/FlightResults/CheckFlightAvailability', {
					selectedFares: selectedfares
				});
			},

			fetchPopup: function (name) {
				return http.GET('/Book/FlightResults/' + name);
			},

			toggleCurrencyMilesService: function (segmentId, isMiles) {
				return http.POST('/Book/FlightResults/toggleCurrencyMiles', null, {
					params: {
						segmentID: segmentId,
						isMiles: isMiles
					}
				});
			},

			updateChangeFlightModel: function () {
				return http.POST('/Book/FlightResults/EditSearch');
			},

			updateSelectedHoldFare: function (selectedFare) {
				return http.POST('/Book/FlightResults/UpdatedSelectedHoldFare', {
					selectedfare: selectedFare
				});
			},

			fetchOnTimePerformanceDetails: function (flightNumbers) {
			    return http.GET('/Book/FlightResults/FetchOnTimePerformanceDetails', {
			        params: {
			            flightNumbers: flightNumbers
			        }
			    })
			}
		};
	}]);

	/*
	 mod.config(['$injector', function ($injector) {
	 var haSessionDataProvider;
	 try {
	 haSessionDataProvider = $injector.get('haSessionDataProvider');
	 haSessionDataProvider.when('POST', mod.constant('SEARCH_FLIGHTS_URL'), function (session) {
	 var data;
	 if (session != null) {
	 data = angular.copy(session.GLOBALS) || {};
	 data.Account = angular.copy(session.Account);
	 data.Discounts = angular.copy(session.Discounts);
	 data.Passengers = angular.copy(session.Passengers);
	 data.Trips = angular.copy(session.FlightResultTrips);
	 //data.Reshop = angular.copy(session.Reshop);
	 }
	 return data;
	 });
	 } catch (e) {}
	 }]);
	 */

})(angular);
;
(function (angular) {

	// Ha Flight Results Service
	// --------------------------------------------
	//
	// * **Class:** haFlightResultsEndOnEndAPI
	// * **Author:** Mari Yamaha
	//
	// Service for fetching flight results

	'use strict';

	var mod = angular.module('haFlightResultsEndOnEndAPI', ['haHttpService']);

	mod.service('haFlightResultsEndOnEndAPI', ['haHttpService', function (http) {
		return {

			selectPromo: function (offerId) {
				return http.POST('/Book/FlightResults/SelectPromo', {
					OfferId: offerId
				});
			},

			removePromo: function () {
				return http.POST('/Book/FlightResults/RemoveSelectedPromo', {});
			},

			SEARCH_FLIGHTS_URL: '/Book/FlightResults/SearchFlightsEndOnEnd',
			fetch: function (/* searchRequestKey */) {
				return http.POST(this.SEARCH_FLIGHTS_URL, { priority: 6 });
			},

			changeMilesOption: function (type) {
				return http.POST('/Book/FlightResults/ChangeMilesOption', {
					pricingType: type
				});
			},

			// temporary method that fetches an alternative tab, in the end the booking dates should just be changed and then use fetch
			SEARCH_FLIGHTS_ALTERNATE_DATE_URL: '/Book/FlightResults/SearchFlightsAlternateDateEndOnEnd',
			fetchTab: function (jdDataList, TripID, SelectedDate /* , searchRequestKey */) {
				return http.POST(this.SEARCH_FLIGHTS_ALTERNATE_DATE_URL, {
					JDDataList: jdDataList,
					tripId: TripID,
					selectedDate: SelectedDate
				});
			},

			SEARCH_FLIGHTS_STEP_THROUGH_URL: '/Book/FlightResults/SearchFlightsStepThroughEndOnEnd',
			SearchFlightsStepThrough: function (jdDataList, TripId, isEdit) {
				return http.POST(this.SEARCH_FLIGHTS_STEP_THROUGH_URL, {
					JDDataList: jdDataList,
					tripId: TripId,
					isEdit: isEdit
				});
			},

			GET_TRIP_SUMMARY_URL: '/Book/FlightResults/FlightResultsTripSummary',
			GetTripSummary: function (jdDataList) {
				return http.POST(this.GET_TRIP_SUMMARY_URL, {
					JDDataList: jdDataList
				});
			},

			// temporary method that fetches an alternative currency result set, in the end the booking currency should just be changed and then use fetch
			fetchCurrency: function () {
				return http.GET('/scripts/modules/BOS_BKK_miles.json');
			},

			bookFlights: function (selectedfares, tripsummary) {
				return http.POST('/Book/FlightResults/ContinueBookingEndOnEnd', {
					selectedFares: selectedfares,
					tripSummary: tripsummary
				});
			},

			// Method that fetch Modal content from sitecore for the passed modal name
			fetchModalContent: function (modalName) {
				return http.GET('/Book/FlightResults/ModalContent', {
					params: {
						modalName: modalName
					}
				});
			},

			checkFlightAvailability: function (selectedfares) {
				return http.POST('/Book/FlightResults/CheckFlightAvailabilityEndOnEnd', {
					selectedFares: selectedfares
				});
			},

			fetchPopup: function (name) {
				return http.GET('/Book/FlightResults/' + name);
			},

			toggleCurrencyMilesService: function (segmentId, isMiles) {
				return http.POST('/Book/FlightResults/toggleCurrencyMiles', null, {
					params: {
						segmentID: segmentId,
						isMiles: isMiles
					}
				});
			},

			updateChangeFlightModel: function () {
				return http.POST('/Book/FlightResults/EditSearch');
			},

			updateSelectedHoldFare: function (selectedFare) {
				return http.POST('/Book/FlightResults/UpdatedSelectedHoldFare', {
					selectedfare: selectedFare
				});
			},

			fetchOnTimePerformanceDetails: function (flightNumbers) {
				return http.GET('/Book/FlightResults/FetchOnTimePerformanceDetails', {
					params: {
						flightNumbers: flightNumbers
					}
				});
			},

			fetchPlaneConfiguration: function () {
				return http.GET('/planes/configuration', { priority: 1 });
			},
			fetchPlaneMarketingData: function () {
				return http.GET('/planes/marketingdata', { priority: 1 });
			},

			getSocialProofData: function (origin, destination, departureDate, flightResultCount) {
				return http.GET('/Book/FlightResults/GetSocialProofData', {
					params: {
						origin: origin,
						destination: destination,
						departureDate: departureDate,
						flightResultCount: flightResultCount
					}
				});
			},

			getRedirectionUrl: function () {
			    return http.GET('/Book/FlightResults/GetFlightResultsRedirectionPath');
			}
		};
	}]);
})(angular);
;
(function (angular) {

	// Ha Global Header Service
	// --------------------------------------------
	//
	// * **Class:** haGlobalHeaderAPI
	// * **Author:** Cinthia Miller & Nathan Probst
	//
	// Service for Global Header


	'use strict';

	var mod = angular.module('haGlobalHeaderAPI', ['haHttpService']);

	mod.service('haGlobalHeaderAPI', ['haHttpService',
		function (http) {
			return {
				selectCountry: function (country, itemId) {
					return http.POST('/Header/SelectCountry', {
						selectedCountry: country,
						contextItemId: itemId
					});
				},

				searchGoogle: function (searchText) {
					return http.POST('/Header/NewSearch', {
						GoogleText: searchText
					});
				}
			};
		}
	]);

})(angular);
;
(function (angular) {

	'use strict';

	var mod = angular.module('haInterstitialAPI', ['haHttpService']);

	mod.service('haInterstitialAPI', ['haHttpService', 'haConfig', function (http, haConfig) {
		return {
			getAirAvailability: function () {
				return http.GET(haConfig.getRazorTemplateUrl('InterstitialAirAvailability'));
			},
			changeSeatInterstitial: function () {
				return http.GET('/my-account/my-trips/Shared/InterstitialUpdateSeats');
			}
		};
	}]);

})(angular);;
(function (angular) {

	'use strict';

	angular.module('haItineraryAPI', ['haHttpService'])

	.service('haItineraryAPI', ['haHttpService', function (http) {
		return {
			updatePassengerInfo: function (pnr, travellerInformation) {
				return http.POST('/MyAccount/ItineraryDetails/UpdatePassengerInfo', {
					pnr: pnr,
					travellerInformation: travellerInformation
				});
			},

			GetReceiptInfo: function (ticketNo) {
				return http.POST('/MyAccount/ItineraryDetails/GetReceiptInfo', {
					ticketNo: ticketNo
				});
			},

			UpdatePaxAndFlightDetails: function (ChangeFlightModel) {
				return http.POST('/MyAccount/ItineraryDetails/UpdatePaxAndFlightDetails', {
					ChangeFlightModel: ChangeFlightModel
				});
			},

			isValidHMAccount: function (hmNumber, firstName, lastName) {
				return http.POST('/MyAccount/ItineraryDetails/IsValidHMAccount', {
					hmNumber: hmNumber,
					firstName: firstName,
					lastName: lastName,
					timeout: 60000
				});
			},

			HMEnrollAndUpdatePNR: function (pnr, travellerInformation) {
				return http.POST('/MyAccount/ItineraryDetails/HMEnrollAndUpdatePassengerInfo', {
					pnr: pnr,
					travellerInformation: travellerInformation,
					timeout: 60000
				});
			},

			IsEligibleForChangeFlight: function () {
				return http.POST('/MyAccount/ItineraryDetails/IsEligibleForChangeFlight');
			},

			CancelHoldTrip: function (cancelHoldTripRQ) {
				return http.POST('/MyAccount/ItineraryDetails/CancelHoldTrip', {
					cancelHoldTripRequest: cancelHoldTripRQ
				});
			},

			GroupPNRSelectTravellers: function (travellers) {
				return http.POST('/MyAccount/ItineraryDetails/GroupPNRSelectTravellers', {
					travellers: travellers
				});
			},
			CancelWaiverPnr: function (pnr, lastName, optionalCancellationEmailAddress) {
				return http.POST('/MyAccount/ItineraryDetails/CancelWaiverPnr', {
					pnr: pnr,
					lastName: lastName,
					optionalCancellationEmailAddress: optionalCancellationEmailAddress
				});
			},

		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haLoginAPI', ['haHttpService'])

	.service('haLoginAPI', ['haHttpService', function (http) {

		return {
			login: function (formData) {
				return http.POST('/MyAccount/Login/Login', formData, {
					headers: {'Content-Type': 'application/x-www-form-urlencoded'}
				});
			},

			pageLoad: function () {
				return http.GET('/MyAccount/Login/AuthenticateLoad?dummy=' + Math.random());
			},

			ResetEmail: function (NewEmailAddress) {
				return http.POST('/MyAccount/Login/ResetEmailAddressUpdate', {
					newEmailAddress: NewEmailAddress
				});
			},

			ResetEmailAddress: function (formData) {
			    return http.POST('/MyAccount/Login/ResetEmailAddressLoad', formData, {
			        headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
			    });
			},

			ValidateSecurityAnswers: function (AccountNo, Question1, Question2, Question3, Answer1, Answer2, Answer3) {
				return http.POST('/MyAccount/Login/ForgotEmailValidateSecurityAnswersSubmit', {
					accountNo: AccountNo,
					question1: Question1,
					question2: Question2,
					question3: Question3,
					answer1: Answer1,
					answer2: Answer2,
					answer3: Answer3
				});
			},

			UpdatePassword: function (formData) {
				return http.POST('/MyAccount/Login/UpdatePassword', formData, {
					headers: {'Content-Type': 'application/x-www-form-urlencoded'}
				});
			},

			ForgotEmailAddressSubmit: function (fname, lname, phoneNo, country, zipCode) {
				return http.POST('/MyAccount/Login/ForgotEmailAddressLoad', {
					firstName: fname,
					lastName: lname,
					phoneNumber: phoneNo,
					country: country,
					zipCode: zipCode
				});
			},

			CreateUsername: function (userName) {
				return http.POST('/MyAccount/Login/CreateUsernameSubmit', {
					username: userName
				});
			},

			AuthenticateEmailOnly: function (email, zip) {
				return http.POST('/MyAccount/EmailOnly/AuthenticateEmailOnly', {
					email: email,
					zip: zip
				});
			},

			EmailOnlyPreferencesSubmit: function (data) {
				return http.POST('/MyAccount/EmailOnly/GetEmailOnlyPreferences', {
					EmailOnlyPreferences: data
				});
			},

			UnsubscribeEmailAddressSubmit: function (EmailAddress, UnSubscriptionID, IsUnsubscribeAll) {
				return http.POST('/MyAccount/EmailOnly/EmailUnsubscribe', {
					EmailAddress: EmailAddress,
					UnSubscriptionID: UnSubscriptionID,
					UnSubscribeAll: IsUnsubscribeAll
				});
			},

			UpdateEmailPreferencesSubmit: function (EmailAddress, ZipCode) {
				return http.POST('/MyAccount/EmailOnly/Unsubscribe', {
					EmailAddress: EmailAddress,
					ZipCode: ZipCode
				});
			}
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Passengers API
	// --------------------------------------------
	//
	// * **Class:** haPassengersAPI
	// * **Author:** Josh Nielsen & Nathan Probst
	//
	// Service for managing passengers

	'use strict';

	var mod = angular.module('haPassengersAPI', ['haHttpService']);

	mod.service('haPassengersAPI', ['haHttpService',
		function (http) {
			return {
				addeditpax: function (guest) {
					return http.POST('/Book/Pax/AddEditPax', {
						travellerInformation: guest
					});
				},

				editPaxLink: function (/* guest */) {
					return http.POST('/Book/Pax/PaxLoadLoggedIn');
				},

				editLoggedInPax: function (guest) {
					return http.POST('/Book/Pax/EditLoggedInPax', {
						editLoggedInTraveler: guest
					});
				},

				addPaxToPNR: function (guest) {
					return http.POST('/Book/Pax/AddPaxToPNR', {
						travellerInformationList: guest
					});
				},

				addExtraLoggedTravellerInfo: function (guest) {
					return http.POST('/Book/Pax/AdditionalLoggedInTravelerInfo', {
						additionalLoggedInTravelerInfo: guest
					});
				},

				associateinfants: function (selectedtravelers) {
					return http.POST('/Book/Pax/AssociateInfants', {
						travellerInformationList: selectedtravelers
					});
				},

				updatecontactinfo: function (contactinfo) {
					return http.POST('/Book/Pax/UpdateContactInfo', {
						ContactInfo: contactinfo
					});
				}
			};
		}
	]);

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haPaymentAPI', ['haHttpService'])

	.service('haPaymentAPI', ['haHttpService', function (http) {
		return {
			FlightAvailability: function (formData) {
				return http.POST('/Book/Payment/FlightAvailability', formData, {
					headers: {'Content-Type': 'application/x-www-form-urlencoded'}
				});
			},

			validateCreditCard: function (formData) {
				return http.POST('/Book/Payment/ValidateCreditCard', formData, {
					headers: {'Content-Type': 'application/x-www-form-urlencoded'}
				});
			},

			ValidatePaymentAuthorization: function (formData) {
				return http.POST('/Book/Payment/ValidatePaymentAuthorization', formData, {
					headers: {'Content-Type': 'application/x-www-form-urlencoded'}
				});
			},

			fetchScheduleMismatchPopup: function () {
				return http.GET('/Book/Payment/FlightScheduleMismatch');
			},

			postPaymentProcess: function (formData, updateSchedule, Token) {
				var url = '/Book/Payment/ProcessPayment?UpdateSchedule=' + updateSchedule + '&Token=' + Token;
				return http.POST(url, formData, {
					headers: {'Content-Type': 'application/x-www-form-urlencoded'}
				});
			},

			postProcessPaymentForAsyncPNR: function (formData, updateSchedule, Token) {
				var url = '/Book/Payment/ProcessPaymentForAsyncPNR?UpdateSchedule=' + updateSchedule + '&Token=' + Token;
				return http.POST(url, formData, {
					headers: {'Content-Type': 'application/x-www-form-urlencoded'}
				});
			},

			postProcessPaymentForHoldPNR: function (formData, updateSchedule, Token) {
				var url = '/Book/Payment/ProcessPaymentForHoldPNR?UpdateSchedule=' + updateSchedule + '&Token=' + Token;
				return http.POST(url, formData, {
					headers: {'Content-Type': 'application/x-www-form-urlencoded'}
				});
			},

			postProcessNITPRedemption: function () {
				var url = '/Book/Payment/ProcessNITPRedemption';
				return http.POST(url, {
					headers: {'Content-Type': 'application/x-www-form-urlencoded'}
				});
			},

			removePriceLockSession: function () {
				return http.POST('/Book/Payment/RemovePriceLockSession');
			},

			getPaymentOptionsState: function () {
				return http.GET('/Book/Payment/GetPaymentMethods');
			},

			fetchModalContent: function (modalName) {
				return http.GET('/Shared/TermsAndConditions/ModalContent', {
					params: {
						modalName: modalName
					}
				});
			},

			getPaymentMethodsState: function () {
			    return http.POST('/Book/Payment/GetPaymentMethods');
			},

			fetchMasterPassToken: function (purchaseType) {
				return http.POST('/Book/MasterPass/GetMasterPassRequestToken', {
					purchaseType: purchaseType
				});
			},

			fetchMasterPassPaymentInfo: function (data) {
				return http.POST('/Book/MasterPass/GetMasterPassPaymentInfo', {
					oauthToken: data.oauth_token,
					oauthVerifier: data.oauth_verifier,
					checkOutUrl: data.checkout_resource_url
				});
			},

			getAliPaySignedUrl: function () {
				return http.POST('/Api/AliPay/GetAliPaySignedUrl');
			}, 

			upgradeToMainCabin: function () {
				return http.POST('/Book/Payment/UpgradeToMainCabin');
			},

			getItineraryDetails: function () {
				return http.GET('/Book/Itinerary/GetItineraryDetails');
			},

			setUpliftToken: function (Token,OrderId) {
				return http.POST('/Book/Payment/SetUpliftToken',{
					Token: Token,
					OrderId: OrderId
				});
			}
		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haPaymentMethodsAPI', ['haHttpService'])

	.service('haPaymentMethodsAPI', ['haHttpService', function (http) {
		return {
			PaymentMethodsAddEdit: function (formData) {
				return http.POST('/MyAccount/PaymentMethods/PaymentMethodAddEdit', formData, {
					headers: {'Content-Type': 'application/x-www-form-urlencoded'}
				});
			},
			PaymentMethodsDelete: function (ccID) {
				return http.POST('/MyAccount/PaymentMethods/PaymentMethodDelete', null, {
					params: {
						ccID: ccID
					}
				});
			},
			PaymentMethodsAddInitialize: function () {
				return http.POST('/MyAccount/PaymentMethods/PaymentMethodAdd');
			},
			PaymentMethodsAdd: function (data) {
				return http.POST('/MyAccount/PaymentMethods/PaymentMethodAddEdit', {
					ccPaymentInfo: data
				});
			},
			getCDESavedCard: function (ccID) {
				return http.GET('/MyAccount/PaymentMethods/GetCDEDataForSavedCard', {
					cache: false,
					params: {
						ccID: ccID
					}
				});
			},
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Global Header Service
	// --------------------------------------------
	//
	// * **Class:** haProfileSettingsAPI
	// * **Author:** Cinthia Miller & Nathan Probst
	//
	// Service for Global Header

	'use strict';

	var mod = angular.module('haProfileSettingsAPI', ['haHttpService']);

	mod.service('haProfileSettingsAPI', ['haHttpService',
		function (http) {
			return {
				updateAccountSettings: function (accountSettingsModel) {
					return http.POST('/MyAccount/ProfileSettings/AccountSettingsSubmit', {
						accountSettingsVM: accountSettingsModel
					});
				},

				updateTravelPreferences: function (formData) {
					return http.POST('/MyAccount/ProfileSettings/TravelPreferencesLoad', formData, {
						headers: {'Content-Type': 'application/x-www-form-urlencoded'}
					});
				},

				updateEmailSubscrption: function (data) {
					return http.POST('/MyAccount/ProfileSettings/EmailSubscriptionsSubmit', {
						emailSubscriptionviewmodel: data
					});
				}
			};
		}
	]);

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haPurchaseMilesAPI', ['haHttpService'])

	.service('haPurchaseMilesAPI', ['haHttpService', function (http) {
		return {
			checkEligibility: function (milesSelected, sourceClient, AccountNumber, LastName, RecipientEmail) {
				return http.POST('/MyAccount/PurchaseMiles/PurchaseMilesEligibility', {
					selectedMiles: milesSelected,
					iSGiftPurchaseSelected: sourceClient,
					recipientAccountNumber: AccountNumber,
					recipientlastName: LastName,
					recipientEmail: RecipientEmail
				});
			}
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Passengers API
	// --------------------------------------------
	//
	// * **Class:** haPassengersAPI
	// * **Author:** Josh Nielsen & Nathan Probst
	//
	// Service for managing passengers

	'use strict';

	var mod = angular.module('haSeatMapAPI', ['haHttpService']);

	mod.service('haSeatMapAPI', ['haHttpService',
		function (http) {
			return {
				//Get seat map for a leg
				getSeatMapForLeg: function (seatmaprequestdata, isPreview) {

					//return http.POST('/Book/InFlightOptions/GetSeatMap', {
					//	seatMapRequest: seatmaprequestdata
					//});

					return http.POST((!isPreview) ? '/Book/InFlightOptions/GetSeatMap' : '/Book/InFlightOptions/GetSeatMapPreview', {
						seatMapRequest: seatmaprequestdata
					});
				},

				//update seat selections to serverUpdateIEditSeatsService
				UpdateInflightOptionsService: function (seatmapmodel, seatmapstring) {
					return http.POST('/Book/InFlightOptions/UpdateInflightOptions', {
						seatmapSelection: seatmapmodel,
						model: seatmapstring
					});
				},

				UpdateChangeFlightInflightoptions: function (seatmapmodel, seatmapstring) {
					return http.POST('/Book/InFlightOptions/UpdateChangeFlightInflightoptions', {
						seatmapSelection: seatmapmodel,
						model: seatmapstring
					});
				},
				ShowDownGradePopUp: function (seatmapmodel) {
					return http.POST('/Book/InFlightOptions/ShowDownGradePopUp', {
						seatmapSelection: seatmapmodel
					});
				},

				//Get seat map for a leg
				getSeatMapForPostLeg: function (seatmaprequestdata) {
					return http.POST('/MyAccount/PostPurchaseEditSeats/GetSeatMap', {
						seatMapRequest: seatmaprequestdata
					});
				},
				//update EditSeats selections to server
				ValidateEditSeatFareDifference: function (seatmapmodel) {
					return http.POST('/MyAccount/PostPurchaseEditSeats/ValidateEditSeatFareDifference', {
						seatMapSelection: seatmapmodel
					});
				},
				ConfirmEditSeatsService: function (seatmapmodel) {
					return http.POST('/MyAccount/PostPurchaseEditSeats/ConfirmEditSeats', {
						model: seatmapmodel
					});
				}
			};
		}
	]);

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haTravelersAPI', ['haHttpService'])

	.service('haTravelersAPI', ['haHttpService', function (http) {
		return {
			TravelerAddEdit: function (formData) {
				return http.POST('/MyAccount/TravelersList/TravelerAddEdit', formData, {
					headers: {'Content-Type': 'application/x-www-form-urlencoded'},
				});
			},

			TravelerDelete: function (travelerID) {
				return http.POST('/MyAccount/TravelersList/TravelerDelete', null, {
					params: {
						travelerID: travelerID
					}
				});
			},

			// New endpoints
			GetTravelers: function (include) {
				return http.GET('/my-account/travelers', {
					params: {
						include: include
					}
				});
			},

			AddEditTraveler: function(travelerData, temp) {
				var params = temp ? { temp: true } : undefined;
				return http.POST('/my-account/travelers/TravelerAddEdit', travelerData, {
					params: params
				});
			},
			getUserType: function () {
				return http.POST('/my-account/Travelers/getUserType');
			}


		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haUserProfileAPI', ['haHttpService'])

	.service('haUserProfileAPI', ['haHttpService', function (http) {
		return {
			checkUsernameAvailability: function (data, canceler) {
				return http.POST('/MyAccount/UserProfile/CheckUsernameAvailability', data, {
					timeout: canceler.promise
				});
			}
		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haPriceApiModule', ['haHttpService'])

	.service('haPriceApiService', ['$http', '$q', '$rootScope', 'haConfig', function ($http, $q, $rootScope, haConfig) {

		var enableGetRequest = $rootScope.$switch('BookingWidget:EnableDealsandOffersGetReq'),
			action = enableGetRequest ? 'GET' : 'POST';

		return {
			fetchInitialPrices: function (options) {
				var fetchInitialUrlFormat = '/Book/LowFareSearchChart/GetPriceChart{0}',
					queryString = enableGetRequest ? '?searchRequest=' + JSON.stringify(options.data.searchRequest) + '&isCalendar=' + JSON.stringify(options.data.isCalendar) : '',
					initialUrl = fetchInitialUrlFormat.format(queryString),
					//initialUrl = enableGetRequest ? haConfig.getDynamicJsonUrl(fetchInitialUrl) : fetchInitialUrl,
					promise = $q.defer(),
					postBody = enableGetRequest ? {} : options.data;

				// GET has a TTL set through Akamai
				$http({ method: action, url: initialUrl, data: postBody }).then(
					function (response) {
						promise.resolve(response.data);
					},
					function (err) {
						promise.reject(err);
					}
				);

				return promise.promise;
			},

			fetchPrices: function (startDate, direction, options) {
				options = options || {};
				var config = options.config;
				var url = options.url || '/Book/LowFareSearchChart/GetPriceChartAdditionalDays';
				return $http.post(url, { dateStr: startDate, direction: direction }, config);
			}
		};
	}]);

})(angular);
;
(function () {
	'use strict';

	angular.module('haSessionTimeoutAPI', []).factory('haSessionTimeoutAPI', haSessionTimeoutApi);

	haSessionTimeoutApi.$inject = ['haHttpService'];

	function haSessionTimeoutApi(http) {
		return {
			getUserSessionTime: getUserSessionTime,
			restartUserSessionTime: restartUserSessionTime
		};

		function getUserSessionTime() {
			return http.GET('/Book/Shared/GetSessionTimeout')
				.then(getSessionTimeComplete)
				.catch(getSessionTimeFailed);

			function getSessionTimeComplete(response) {
				return response.data;
			}

			function getSessionTimeFailed(error) {
				console.log('XHR Failed for getSessionTime.' + error.data);
			}
		}

		function restartUserSessionTime() {
			return http.POST('/json/v1/session/extend')
				.then(restartSessionTimeComplete)
				.catch(restartSessionTimeFailed);

			function restartSessionTimeComplete(response) {
				return response;
			}

			function restartSessionTimeFailed(error) {
				console.log('XHR Failed for Extend Session: ' + error.status);
				return error;
			}
		}
	}

})();;
// Ha Geo Data API
// ---------------
//
// * **Class:** haGeoDataAPI
// * **Author:** Nathan Probst
//
// Service for fetching geo data

(function (angular) {
	'use strict';

	var module;
	try {
		module = angular.module('haGeoDataModule');
	} catch (e) {
		module = angular.module('haGeoDataModule', ['haHttpService']);
	}

	module.service('haGeoDataAPI', ['haHttpService', 'haConfig', function (http, config) {
		return {
			GET_COUNTRIES_URL: function () {
				return '/MyAccount/AccountShared/GetCountries' +
					'?languagecode=' + config.getLanguageCode();
			},
			getCountries: function () {
				return http.GET(this.GET_COUNTRIES_URL())
				.then(function (response) {
					return response.data;
				});
			},
			GET_STATES_URL: function (countryKey) {
				return '/MyAccount/AccountShared/GetStatesByCountry' +
					'?countrykey=' + countryKey +
					'&languagecode=' + config.getLanguageCode();

			},
			getStates: function (countryKey) {
				return http.GET(this.GET_STATES_URL(countryKey))
				.then(function (response) {
					return response.data;
				});
			},
			GET_STATE_AND_CITIES_URL: function (countryKey, postalCode) {
				return '/MyAccount/AccountShared/GetCityAndStateByCountryAndPostalCode' +
					'?countrykey=' + countryKey +
					'&postalcode=' + postalCode +
					'&languagecode=' + config.getLanguageCode();
			},
			getStateAndCities: function (countryKey, postalCode) {
				return http.GET(this.GET_STATE_AND_CITIES_URL(countryKey, postalCode))
				.then(function (response) {
					return response.data;
				});
			}
		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haEncryptionModule')
		.service('haEncryptionAPI', ["haHttpService", function (http) {

			var encryptionAPIPath = window.location.protocol + '//' + window.location.host + "/api/v2/Encryption";

			return {
				encryptString: function (dataToEncrypt, key) {
					return http.GET(encryptionAPIPath+"/String/"+dataToEncrypt+"/"+key).success(function (response) {
						return Promise.resolve(response);
					}).error(function (error) {
						return Promise.reject(new Error(error))
					});
				}
			}
		}]);

})(angular);
;
(function (angular) {

	'use strict';

	angular.module('haLaunchDarklyModule')
		.service('haLaunchDarklyAPI', ["haHttpService", function (http) {

			var launchDarklyAPIPath = window.location.protocol + '//' + window.location.host+"/api/v2/LaunchDarkly";

				return {
					getFeatureFlag: function (key, type, variant,useCache) {
						 return http.GET(launchDarklyAPIPath+"/"+key+"/"+type+"/"+variant+"/"+useCache).success(function(data){
							//console.log(key+":"+data)
							return Promise.resolve(data)
						 }).error(function(error){
							return Promise.reject(new Error(error))
						 });
					},
					trackResult: function(id) {
						return http.GET(launchDarklyAPIPath+"/TrackResult/"+id).success(function(data){
							return Promise.resolve(data)
						 }).error(function(error){
							return Promise.reject(new Error(error))
						 });
					}

				}
			}
		]);

})(angular);
;
(function (ng) {

	// Hawaiian Airport Pair Directive
	// ===============================
	//
	// * **Class:** HaAirportPair
	// * **Author:** Nathan Probst
	//
	// Provide communication between two ha-airport-input directives.

	'use strict';

	var module;
	try {
		module = ng.module('haAirportsModule');
	} catch (e) {
		module = ng.module('haAirportsModule', ['haCitiesModule', 'haAdvanceToNextInputService', 'ui.bootstrap.typeahead.ha']);
	}

	module.directive('haAirportPair', [function () {
		var haAirportPairCtrl = ['$scope', function () {

			var ctrl = {
				originCtrl: null,
				destinationCtrl: null,
				register: function (inputCtrl) {
					inputCtrl.pairCtrl = ctrl;
					if (inputCtrl.isOrigin) {
						// $log.debug('originCtrl', inputCtrl);
						ctrl.originCtrl = inputCtrl;
					} else if (inputCtrl.isDestination) {
						// $log.debug('destinationCtrl', inputCtrl);
						ctrl.destinationCtrl = inputCtrl;
					} else {
						throw new Error('Attempted to register a non-origin/destination inputCtrl.');
					}
				},
				getOriginCode: function () {
					return ctrl.originCtrl != null ? ctrl.originCtrl.getCode() : null;
				},
				getDestinationCode: function () {
					return ctrl.destinationCtrl != null ? ctrl.destinationCtrl.getCode() : null;
				}
			};

			return ctrl;
		}];

		return {
			restrict: 'A',
			scope: true,
			controller: haAirportPairCtrl
		};
	}]);

})(angular);
;
(function (ng) {

	// Hawaiian Airport Input Directive
	// ================================
	//
	// * **Class:** HaAirportInput
	// * **Author:** Nathan Probst
	//
	// Provide a intelligent input for HA-served airports.

	'use strict';

	var module;
	try {
		module = ng.module('haAirportsModule');
	} catch (e) {
		module = ng.module('haAirportsModule', ['haCitiesModule', 'haAdvanceToNextInputService', 'ui.bootstrap.typeahead.ha']);
	}

	module.controller('haAirportInputCtrl', [
		'$log',
		'$scope',
		'$timeout',
		'haTemplateCache',
		'haCitiesSvc',
		'haConfig',

		function ($log, $scope, $timeout, haTemplateCache, haCitiesSvc, haConfig) {
			// Pre-Load template contents on initial load to the templateCache for faster rendering of modal popup
			$timeout(function () {
				haTemplateCache.get(haConfig.getRazorTemplateUrl('destination-modal-base'));
				haTemplateCache.get(haConfig.getRazorTemplateUrl('destination-modal-hawaii'));
				haTemplateCache.get(haConfig.getRazorTemplateUrl('destination-modal-na'));
				haTemplateCache.get(haConfig.getRazorTemplateUrl('destination-modal-asia'));
			}, 500 + 1000 * Math.random());

			var ctrl = {
				isOrigin: false,
				isDestination: false,
				isReshop: false,
				isExpertBooking: false,
				legIndex: 0,
				pairCtrl: null
			};

			var getPairCode = function () {
				if (ctrl.pairCtrl != null) {
					if (ctrl.isDestination) {
						return ctrl.pairCtrl.getOriginCode();
					} else if (ctrl.isOrigin) {
						return ctrl.pairCtrl.getDestinationCode();
					}
				}
				return null;
			};

			ctrl.getCode = function () {
				return $scope.ngModel ? $scope.ngModel.Code : null;
			};

			$scope.getMatchingAirports = function (nameOrCode) {
				if (ctrl.isReshop) {
					return haCitiesSvc.getMatchingFilteredCities(nameOrCode, ctrl.legIndex, ctrl.isOrigin);
				}

				if (ctrl.isExpertBooking) {
					return haCitiesSvc.getMatchingCitiesExpertBooking(nameOrCode, getPairCode(), $scope.filterFn);
				}

				// return all matching cities, not just valid pairs, per pbi 60
				return haCitiesSvc.getMatchingCities(nameOrCode, null, $scope.filterFn);

			};

			return ctrl;
		}
	]);

	module.filter('isHACity', function () {
		return function filterFn(city) {
			return (city != null) && (city.IsHACity === true);
		};
	});

	module.directive('haAirportInput', [
		'$log',
		'$timeout',
		'$window',
		'haConfig',

		function ($log, $timeout, $window) {
			return {
				restrict: 'A',
				require: ['haAirportInput', '?^haAirportPair'],
				scope: {
					ngModel: '=',
					preselectCode: '=',
					onChange: '@',
					filterFn: '@',
					label: '@',
					placeholder: '@',
					required: '@',
					disabled: '@',
					id: '@',
					name: '@',
					hidePin: '@',
					labelStyle: '@',
					size: '@'
				},
				// !!! if modifying below template, update the source file, ha-airport-input-template.html. !!!
				// !!! Follow the instruction in the file to minify the code and update the template. !!!
				template: "<div> <div has-autofill ng-class=\"{invalid: (invalid && !isFocused)}\"> <label class=\"ha-label\" ng-class=\"{'required-asterisk':required, 'ha-form-lg': !size || size=='large', 'ha-form-sm':size=='small', 'inline': labelStyle=='inline' || !labelStyle || labelStyle !=='eyebrow', 'wrap-on-mobile': labelStyle=='inlineWrapOnMobile'}\"> <span ng-if=\"label\">{{label}}<span class=\"mandatory-flag\" ng-if=\"required\">*</span></span> <input type=\"hidden\" name=\"{{name}}Code\" ng-value=\"ngModel.Code\"/> <div class=\"mobile-tap-mask\" ng-if=\"$root.isMobile\" ha-location-modal=\"hawaii\" ng-click=\"pinClicked()\" filter-fn=\"{{filterFn}}\"></div><input role=\"combobox\" aria-owns=\"airportMatch-{{elNumber}}\" aria-expanded=\"{{isOpen() || false}}\" placeholder=\"{{placeholder}}\"  id=\"{{id}}\" type=\"text\" name=\"{{name}}\" autocomplete=\"off\" ng-model=\"ngModel\" allow-non-english=\"true\" ha-typeahead=\"airport as airport.DisplayName for airport in getMatchingAirports($viewValue)\" typeahead-editable=\"false\" typeahead-min-length=\"3\" typeahead-wait-ms=\"150\" typeahead-on-select=\"onSelect($item, $model, $label)\" ng-required=\"required\" ng-disabled=\"disabled\" ng-focus=\"onFocus($event)\" ng-blur=\"onBlur($event)\" ng-class=\"{'has-pin':!hideWhereweflyPin}\" ha-errors > <div class=\"location-dropdown\"> <div class=\"ha-typeahead-results\" role=\"textbox\" id=\"airportMatch-{{elNumber}}\"></div></div><a href=\"\" ha-location-modal=\"hawaii\" ng-if=\"!hideWhereweflyPin\" ng-click=\"pinClicked()\" title=\"{{pinTitle || label}}\" filter-fn=\"{{filterFn}}\" city-list-type=\"{{cityListType}}\" class=\"ha-airport-input-pin\"> <i class=\"ha-icon fontIcon32-mapPin\"></i> </a> <em for=\"airportInput-{{elNumber}}\"></em> </label> </div></div>",
				//templateUrl: haConfig.getTemplateUrl('ha-airport-input-template.html'),
				controller: 'haAirportInputCtrl',
				link: function ($scope, $el, $attrs, $ctrls) {

					// make the input element readonly if on mobile to prevent keyboards from popping up on some android devices
					if ($scope.$root.isMobile) {
						$el.find('input[role="combobox"]').attr('readonly','readonly');
					}

					$scope.elNumber = Math.floor((Math.random() * 10000) + 1);
					var haAirportInputCtrl = $ctrls[0];
					haAirportInputCtrl.isOrigin = (typeof $attrs.origin !== 'undefined');
					haAirportInputCtrl.isDestination = (typeof $attrs.destination !== 'undefined');
					haAirportInputCtrl.isReshop = (typeof $attrs.reshop !== 'undefined');
					haAirportInputCtrl.isExpertBooking = (typeof $attrs.expertBooking !== 'undefined');
					haAirportInputCtrl.legIndex = ($attrs.legIndex != null) && $attrs.legIndex;

					$scope.cityListType = 'normal';
					if (haAirportInputCtrl.isReshop) {
						if (haAirportInputCtrl.isOrigin) {
							$scope.cityListType = 'reshop-' + haAirportInputCtrl.legIndex + '-origin';
						} else {
							$scope.cityListType = 'reshop-' + haAirportInputCtrl.legIndex + '-destination';
						}
					} else if (haAirportInputCtrl.isExpertBooking) {
						$scope.cityListType = 'expertbooking';
					}

					var haAirportPairCtrl = $ctrls[1];
					if (haAirportPairCtrl != null) {
						// $log.debug('haAirportPairCtrl', haAirportPairCtrl);
						haAirportPairCtrl.register(haAirportInputCtrl);
					}

					// Allow pre-selection with only an airport code
					if ($scope.preselectCode != null) {
						var pcode = $scope.preselectCode;
						$scope.getMatchingAirports(pcode).then(function (cityList) {
							for (var i = 0; i < cityList.length; i++) {
								if (cityList[i].Code === pcode) {
									$scope.ngModel = cityList[i];
									break;
								}
							}
						});
					}

					// To allow redirect on selection...
					$scope.$parent.setLocationPathname = function (newUrl) {
						if ($window.location.pathname !== newUrl) {
							console.log('$window.location.pathname = ' + newUrl);
							$window.location.pathname = newUrl;
						}
					};

					// To allow redirect on selection...
					$scope.$parent.setLocationPathname = function (newUrl) {
						if ($window.location.pathname !== newUrl) {
							console.log('$window.location.pathname = ' + newUrl);
							$window.location.pathname = newUrl;
						}
					};

					$scope.$watch('ngModel', function (newValue) {
						if ($scope.onChange != null && newValue) {
							$scope.$parent.$eval($scope.onChange);
						}
						$scope.$emit('airportChanged');
					});

					// fires when coming from modal window
					$scope.onSelected = function (city) {
						$scope.ngModel = city;
						$timeout(function () {
							$scope.focus();
						}, 0);
					};

					// fires when clicking or tabbing a highlighted result
					$scope.onSelect = function () {
						$timeout(function () {
							$scope.focus();
						}, 0);
						//$scope.$emit('haAdvanceToNextInput:next', $el);
					};

					$scope.pinClicked = function () {
						$scope.$emit('haWhereWeFlyPinClicked');
					};

					$scope.onClick = function ($event) {
						// select input value on click
						$event.target.select();
					};

					$scope.hideWhereweflyPin = $scope.$eval($scope.hidePin);
					$scope.labelStyle = $scope.$eval($scope.labelStyle);
					$scope.size = $scope.$eval($scope.size);

					$scope.focus = function () {
						setTimeout(function () {
							$el.find('input[type="text"]').focus();
						}, 0);
					};

					if ('pinTitle' in $attrs) {
						$scope.pinTitle = $attrs.pinTitle;
					} else {
						$scope.pinTitle = $scs('BookingWidget.AirportInputPinTitle');
					}

					$scope.onFocus = function ($event) {
						$scope.isFocused = true;
						$timeout(function () {
							$event.target.select();
						}, 0);
						$scope.$emit('airportInputFocused', $el);
						// $log.log('focused!');
					};

					$scope.onBlur = function () {
						$scope.isFocused = false;
						// $timeout(function () {
						//     $scope.isFocused = $scope.invalid = false;
						//     $el.find('.ha-input').removeClass('invalid');
						// }, 250);
						// $timeout(function () {
						//     $scope.invalid = (input.className.indexOf('ng-invalid') > -1);
						// }, 500);
						// $log.log('blurrred!');
					};

				}
			};
		}
	]);

})(angular);
;
(function (angular) {

	'use strict';

	var module = angular.module('haStickyBookingWidgetModule', ['haModalService']);

	module.directive('haStickyBookingWidget', ['$rootScope', '$window', 'haUtils', 'haCitiesSvc', 'haConfig', 'haDateUtils', '$timeout', 'haModal', 'haSitecoreStrings', function ($rootScope, $window, haUtils, haCitiesSvc, haConfig, haDateUtils, $timeout, haModal, $scs) {

		var HaStickyBookingWidgetLink = function ($scope, $el, $attrs) {

			// Scope functions
			$scope.openFlightSearchEdit = function () {
				haModal(haConfig.getTemplateUrl('ha-book-flight-search-edit-modal.html'), {
					id: 'fare-help-modal',
					backdrop: 'false',
					scope: $scope,
					modalLock: true
				});
			};

			// Local functions
			var stickyBookingOffsetY = function () {
				if ($scope.stickyBookingOffsetY === false) {
					var el = angular.element('[ha-sticky-booking-widget]');
					if (el != null) {
						$scope.stickyBookingOffsetY = el.offset().top;
					} else {
						return;
					}
				}
				return $scope.stickyBookingOffsetY;
			};

			// Recall search
			$scope.recallSearch = function (idx) {
				$scope.$broadcast('recallSearch', idx);
				$scope.recent = false;
			};


			// Sticky scrolling
			angular.element($window).bind('scroll', function () {
				var yOffset = this.pageYOffset;
				if ($('html').hasClass('lte-ie8')) {
					yOffset = this.document.documentElement.scrollTop;
				}
				if (yOffset >= stickyBookingOffsetY()) {
					$scope.fixed = true;
				} else {
					$scope.fixed = false;
				}
				$scope.$digest();
			});


			// WATCHERS
			//*******************
			// Watch recent open/close
			// Watch recent open/close
			$scope.recentSearches = {open: false};
			$scope.$watch('recentSearches.open', function (isOpen) {
				if (typeof isOpen === 'undefined') {
					return;
				}

				if (isOpen) {
					$timeout(function () {
						$('body').on('click.recent', function () {
							$scope.$apply(function () {
								$scope.recentSearches.open = false;
							});
						});
					}, 10);

				} else {
					$('body').off('click.recent');
				}
			});


			// INITIALIZATION
			//*******************
			function initialize() {

				// Interstitial default image
				$attrs.$observe('defaultDestinationImage', function (val) {
					$scope.defaultDestinationImage = val || '';
				});
				//	multiCity - allows multi-city trips.  Default is on.
				$attrs.$observe('multiCity', function (val) {
					$scope.multiCity = (val && val === 'false') ? false : true;
				});

				// State
				$scope.stickyBookingOffsetY = false;

				// Legs
				$scope.leg = {};

				// Cookie Processing and Defaults
				var flightQueryCookie = haUtils.getFlightQueryModelCookie();
				var dateToday = moment().format('YYYY-MM-DD');
				var recentSearchArr = haUtils.getFlightQueryModelRecentCookie();

				if (recentSearchArr) {
					//take past dates out
					for (var i = 0; i < recentSearchArr.length; i++) {

						if (recentSearchArr[i].FlightSearchSegmentList[0].DepartureDate.substr(0, 10) < dateToday) {
							recentSearchArr.splice(i, 1);
						}
						$scope.flightQueryCookieArr = recentSearchArr;
					}
				}

				if (flightQueryCookie && flightQueryCookie.FlightSearchSegmentList[0] && flightQueryCookie.FlightSearchSegmentList[0].OriginCityCode) {
					haCitiesSvc.getCityByCode(flightQueryCookie.FlightSearchSegmentList[0].OriginCityCode).then(function (airport) {
						$scope.leg.origin = airport;
					});
				} else {
					$scs.get("BookingWidget['originplaceholderstickywidget']").then(function (txt) {
						$scope.leg.origin = txt;
					});
				}
				if (flightQueryCookie && flightQueryCookie.FlightSearchSegmentList[0] && flightQueryCookie.FlightSearchSegmentList[0].DestinationCityCode) {
					haCitiesSvc.getCityByCode(flightQueryCookie.FlightSearchSegmentList[0].DestinationCityCode).then(function (airport) {
						$scope.leg.destination = airport;
					});
				} else {
					$scs.get("BookingWidget['destinationplaceholderstickywidget']").then(function (txt) {
						$scope.leg.destination = txt;
					});
				}
			}

			initialize();
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaStickyBookingWidgetLink,
			templateUrl: haConfig.getTemplateUrl('ha-sticky-booking-widget-base-template.html')
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Progress Bar Directive
	// --------------------------------------------
	//
	// * **Class:** HaProgressBar
	// * **Author:** Jake Albaugh
	//
	// a dynamic progress bar

	'use strict';

	var mod = angular.module('haProgressBarModule', []);

	mod.directive('haProgressBar', ['haConfig', function (haConfig) {

		var HaProgressBarController = function () {

		};

		HaProgressBarController.$inject = ['$scope'];

		var HaProgressBarLink = function ($scope, $el, $attrs) {

			$scope.steps = $attrs.steps;
			$scope.current = $attrs.current;
			$scope.completed = $scope.current - 1;
			$scope.empty = $scope.steps - $scope.current;

			$scope.getIterations = function (its) {
				return new Array(its);
			};

			$scope.$emit('$haProgressBarReady');
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaProgressBarLink,
			controller: HaProgressBarController,
			templateUrl: haConfig.getTemplateUrl('ha-progress-bar-base-template.html')
		};
	}]);

})(angular);;
(function (angular) {

	// Ha Error Page Directive
	// --------------------------------------------
	//
	// * **Class:** HaErrorPage
	// * **Author:** Jake Albaugh
	//
	// A customizable error page supporting a title and description, as well as room for custom html to be transcluded beneath the title.

	'use strict';

	var mod = angular.module('haErrorPageModule', []);

	mod.directive('haErrorPage', ['haConfig', function (haConfig) {

		var HaErrorPageController = function () {

		};

		HaErrorPageController.$inject = ['$scope'];

		var HaErrorPageLink = function ($scope, $el, $attrs) {

			$scope.header = $attrs.header;
			$scope.subtext = $attrs.subtext;
			$scope.hasTransclude = (angular.element('[ng-transclude]').contents().length > 0) ? true : false;

			return {
				restrict: 'A',
				scope: true,
				link: HaErrorPageLink,
				controller: HaErrorPageController,
				transclude: true,
				templateUrl: haConfig.getTemplateUrl('ha-error-page-base-template.html')
			};
		};
	}]);

})(angular);;
(function (angular) {

	// Ha Map Navigator Directive
	// --------------------------------------------
	//
	// * **Class:** HaMapNavigator
	// * **Author:** Chad Kumabe
	//
	// Map Navigation Component for Route Map

	/* global Hammer */

	'use strict';

	var mod = angular.module('haMapNavigatorModule', []);

	mod.directive('haMapNavigator', ['haConfig', function (haConfig) {

		var HaMapNavigatorController = function ($scope, $window) {
			$window.onresize = function () {
				$scope.reinitializeMap();
				$scope.$apply();
			};
		};

		HaMapNavigatorController.$inject = ['$scope', '$window'];

		var HaMapNavigatorLink = function ($scope, $el, $attr) {

			var magnifyBorder = 100;
			var previewWindowWidth = 172;
			var previewWindowHeight = 0;
			$scope.zoomLevel = 0.75;
			var maxZoomLevel = $scope.zoomLevel;
			var origMainImageWidth = 0;
			var moveRatio = 0;
			var model = $scope.$eval($attr.model);
			var magnifier = $el.find('.focus-magnifier');
			var mainImageContainer = $el.find('.main-image-container');
			var mainOffsetHeight = 0;
			var mainOffsetWidth = 0;
			var viewportHeight = $el.width() / 2;
			var viewportRatio = 0.5;
			var zoomResolution = 0.2;

			$scope.model = model;
			var calculateMoveRatio = function () {
				moveRatio = parseInt($el.css('width'), 10) / previewWindowWidth / $scope.zoomLevel;
			};

			var updateMagnifier = function () {
				var origWidth = parseInt(magnifier.width(), 10);
				var newWidth = $scope.zoomLevel * previewWindowWidth;
				var origHeight = parseInt(magnifier.height(), 10);
				var newHeight = newWidth * viewportRatio;

				$scope.magnifierStyle = {
					width: newWidth + 'px',
					height: newHeight + 'px',
					borderWidth: Math.round(magnifyBorder / $scope.zoomLevel) + 'px'
				};
				// allow for center zooming
				var magnifierTopOffset = parseInt(magnifier.css('top'), 10) +
					((origHeight - newHeight) / 2);
				var magnifierLeftOffset = parseInt(magnifier.css('left'), 10) +
					((origWidth - newWidth) / 2);

				// adjust if not in bounds
				if (magnifierLeftOffset + newWidth > previewWindowWidth) {
					magnifierLeftOffset = previewWindowWidth - newWidth;
				}
				if (magnifierTopOffset + newHeight > previewWindowHeight) {
					magnifierTopOffset = previewWindowHeight - newHeight;
				}
				if (magnifierLeftOffset < 0) {
					magnifierLeftOffset = 0;
				}
				if (magnifierTopOffset < 0) {
					magnifierTopOffset = 0;
				}
				magnifier.css('top', magnifierTopOffset + 'px');
				magnifier.css('left', magnifierLeftOffset + 'px');
			};
			var updateMapZoom = function () {
				var origMapWidth = $el.find('.main-image').width();
				$scope.mapImageStyle = {
					width: parseInt($el.css('width'), 10) / $scope.zoomLevel
				};
				var newMapImageHeight = parseInt($el.find('.main-image').height(), 10) *
					($scope.mapImageStyle.width / origMapWidth);
				mainOffsetHeight = newMapImageHeight - viewportHeight;//parseInt($el.height(),10);
				mainOffsetWidth = $scope.mapImageStyle.width - parseInt($el.css('width'), 10);
				$scope.mainImageConstraintStyle = {
					width: $scope.mapImageStyle.width * 2 - parseInt($el.css('width'), 10),
					height: newMapImageHeight * 2 - viewportHeight,//parseInt($el.height(),10),
					left: -(mainOffsetWidth),
					top: -(mainOffsetHeight)
				};

			};

			var updateFocusContainer = function () {
				var borderWidth = Math.round(magnifyBorder / $scope.zoomLevel);
				var newWidth = (borderWidth * 2) + previewWindowWidth;
				var newHeight = (borderWidth * 2) + previewWindowHeight - 1;
				$scope.focusContainerStyle = {
					top: -borderWidth + 'px',
					left: -borderWidth + 'px',
					width: newWidth + 'px',
					height: newHeight + 'px'
				};
			};


			var repositionMainMap = function () {
				var leftOffset = parseInt(magnifier.css('left'), 10) * moveRatio;
				var topOffset = parseInt(magnifier.css('top'), 10) * moveRatio;
				mainImageContainer.css('left', -(leftOffset - mainOffsetWidth) + 'px');
				mainImageContainer.css('top', -(topOffset - mainOffsetHeight) + 'px');
			};

			var repositionMagnifier = function () {
				var leftOffset = (parseInt(mainImageContainer.css('left'), 10) - mainOffsetWidth) / moveRatio;
				var topOffset = (parseInt(mainImageContainer.css('top'), 10) - mainOffsetHeight) / moveRatio;
				magnifier.css('left', -leftOffset + 'px');
				magnifier.css('top', -topOffset + 'px');
			};

			var updateNavComponents = function () {
				calculateMoveRatio();
				updateMagnifier();
				updateFocusContainer();
				updateMapZoom();
				repositionMainMap();
			};


			$scope.zoomOut = function () {
				if ($scope.zoomLevel < 1) {
					$scope.zoomLevel = $scope.zoomLevel + zoomResolution <= 1 ?
					$scope.zoomLevel + zoomResolution : 1;
					updateNavComponents();
				}
			};

			$scope.zoomIn = function () {
				if ($scope.zoomLevel > maxZoomLevel) {
					$scope.zoomLevel = $scope.zoomLevel - zoomResolution >= maxZoomLevel ?
					$scope.zoomLevel - zoomResolution : maxZoomLevel;
					updateNavComponents();
				}
			};

			var imagesSet = 0;
			var asyncSetInitPos = function () {
				// position after all images set
				// currently waiting for only 2 images set
				imagesSet++;
				if (imagesSet === 2) {
					// hardcode position to focus on hawaii
					magnifier.css('left', '28px');
					magnifier.css('top', '13px');
					repositionMainMap();
					$scope.$apply();
				}
			};

			var mainImageId = $el.find('.main-image').attr('image-id');
			var initializeMainMap = function () {
				$el.css('height', viewportHeight);
				$el.find('.ha-map-navigator').css('height', viewportHeight);
				maxZoomLevel = $el.width() / origMainImageWidth;
				if (maxZoomLevel > $scope.zoomLevel) {
					$scope.zoomLevel = maxZoomLevel;
				}
				calculateMoveRatio();
				updateMapZoom();
				asyncSetInitPos();
			};
			$scope.$on(mainImageId, function () {
				origMainImageWidth = $el.find('.main-image').width();
				initializeMainMap();
			});

			var previewBackgroundId = $el.find('.preview-background').attr('image-id');
			var initializePreviewWindow = function () {
				previewWindowWidth = $el.find('.preview-background').width();
				previewWindowHeight = $el.find('.preview-background').height();
				$el.find('.preview-container').css('height', previewWindowHeight + 1);
				$el.find('.tap-zoom-panel').css('height', previewWindowHeight + 1);
				updateMagnifier();
				updateFocusContainer();
				asyncSetInitPos();
			};

			$scope.$on(previewBackgroundId, function () {
				$el.find('.preview-background')[0].ondragstart = function () {
					return false;
				};
				initializePreviewWindow();
			});

			$el.find('[ha-draggable]').each(function () {
				var scope = angular.element(this).scope();
				scope.updateDraggableSettings({
					containment: 'parent'
				});
			});

			$scope.$on('$dragging', function (ev, ui) {
				if ($(ui.toElement).hasClass('main-image')) {
					repositionMagnifier();
				} else {
					repositionMainMap();
				}
			});

			$scope.reinitializeMap = function () {
				viewportHeight = $el.width() / 2;
				initializePreviewWindow();
				initializeMainMap();
				updateNavComponents();
			};

			// mouse states
			$scope.setGrab = function (isGrab) {
				$scope.grabbing = isGrab;
			};

			// add scroll zoom handler
			new window.Hamster($el.find('.focus-magnifier')[0]).wheel(function (ev, delta) {
				if (delta > 0) {
					$scope.zoomIn();
				}
				else {
					$scope.zoomOut();
				}
				$scope.$apply();
			});

			new Hammer($el.find('.main-image-container')).on('pinchin', function () {
				$scope.zoomOut();
				$scope.$apply();
			});

			new Hammer($el.find('.main-image-container')).on('pinchout', function () {
				$scope.zoomIn();
				$scope.$apply();
			});

			$scope.$emit('$haMapNavigatorReady');
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaMapNavigatorLink,
			templateUrl: haConfig.getTemplateUrl('ha-map-navigator-base-template.html'),
			controller: HaMapNavigatorController
		};
	}]);

	mod.directive('loadWatch', function () {
		return {
			restrict: 'A',
			link: function (scope, element, attrs) {
				element.bind('load', function (ev) {
					scope.$emit(attrs.imageId, ev);
				});
			}
		};
	});

})(angular);
;
(function (angular) {

	// Ha Global Message Directive
	// --------------------------------------------
	//
	// * **Class:** HaGlobalMessage
	// * **Author:** Chad Kumabe
	//
	// Global Message used for pages

	'use strict';

	var module = angular.module('haGlobalMessageModule', ['ngAnimate', 'haAlertModule']);

	module.directive('haGlobalMessage', ['haConfig', '$timeout', function (haConfig, $timeout) {

		var HaGlobalMessageController = function ($scope, $timeout) {
			$scope.alert = {
				type: 'success',
				header: '',
				description: null
			};
			$scope.closeMessage = function () {
				$scope.isShown = false;
			};
			$scope.setAutoFade = function () {
				$timeout($scope.closeMessage, $scope.messageCloseTime);
			};
		};

		HaGlobalMessageController.$inject = ['$scope', '$timeout'];

		var HaGlobalMessageLink = function ($scope, $el, $attrs) {
			$scope.alert.header = $attrs.header;
			$scope.alert.description = $attrs.description;
			$scope.alert.type = $attrs.type;
			$scope.isShown = !$scope.alert.header || $scope.alert.header === '' ? false : true;
			$scope.messageCloseTime = $attrs.messageCloseTime || 3000;
			$scope.setAutoFade();
			$scope.exampleMethod = function () {
				return $scope;
			};

			$scope.$emit('$haGlobalMessageReady');

			$timeout(function () {
				$el.children().find('[role="alert"]').focus();
			}, 300);
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaGlobalMessageLink,
			templateUrl: haConfig.getTemplateUrl('ha-global-message-base-template.html'),
			controller: HaGlobalMessageController
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Global Alert Directive
	// --------------------------------------------
	//
	// * **Class:** HaGlobalAlert
	// * **Author:** Melissa Rota
	//
	// Global alert that can appear across the top of all pages.

	'use strict';

	var module = angular.module('haGlobalAlertModule', []);

	module.directive('haGlobalAlert', ['haConfig', function (haConfig) {

		var HaGlobalAlertController = function ($scope) {

			$scope.showAlert = true;

			$scope.$on('$showAlert', function () {
				$scope.showAlert = true;
			});

			$scope.$on('$hideAlert', function () {
				$scope.showAlert = false;
			});

			$scope.hideAlert = function () {
				$scope.showAlert = false;
			};
		};

		HaGlobalAlertController.$inject = ['$scope'];

		var HaGlobalAlertLink = function ($scope, $el, $attrs) {

			$scope.type = $attrs.type;

			$scope.noIcon = false;
			if ($attrs.noicon) {
				$scope.noIcon = true;
			}

			$scope.header = $attrs.header;
			$scope.description = $attrs.description;
			$scope.btntext = $attrs.btntext;
			$scope.btnlink = $attrs.btnlink;

			$attrs.$observe('header', function () {
				$scope.header = $attrs.header;
				$scope.showAlert = true;
			});

			$attrs.$observe('type', function () {
				$scope.type = $attrs.type;
				$scope.showAlert = true;
			});

			$attrs.$observe('description', function () {
				$scope.description = $attrs.description;
			});

			$attrs.$observe('noicon', function () {
				$scope.noIcon = false;
				if ($attrs.noicon) {
					$scope.noIcon = true;
				}
				$scope.showAlert = true;
			});

			$attrs.$observe('btntext', function () {
				$scope.btntext = $attrs.btntext;
			});

			$attrs.$observe('btnlink', function () {
				$scope.btnlink = $attrs.btnlink;
			});

			$scope.$emit('$haGlobalAlertReady');
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaGlobalAlertLink,
			templateUrl: haConfig.getTemplateUrl('ha-global-alert-base-template.html'),
			controller: HaGlobalAlertController
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Tooltip Directive
	// --------------------------------------------
	// *
	// * **Class:** HaTooltip
	// * **Author:** Cory Shaw
	//
	// Shows a blue tooltip with some options next to formfields, etc

	'use strict';

	var module = angular.module('haTooltipModule', []);

	module.directive('haTooltip', ['haConfig', '$timeout', function (haConfig, $timeout) {

		var HaTooltipController = function () {

		};

		HaTooltipController.$inject = ['$scope'];

		var HaTooltipLink = function ($scope, $el, $attrs) {
			$scope.subtext = $attrs.subtext;

			$scope.position = $attrs.position;
			$scope.arrowPosition = $attrs.arrowPosition;
			var width = $attrs.width;
			if (width > window.innerWidth) {
				width = window.innerWidth- 20;
			}
			$scope.width = width;

			$scope.targetElId = $attrs.targetElId;

			$scope.setWidth = function () {
				if ($scope.width) {
					return {width: $scope.width};
				}
			};

			if ($scope.width) {
				$el.css({width: $scope.width});
			}

			var arrowPosition;
			// Dynamic positioning based on a target element
			if ($scope.targetElId && $scope.position === 'absolute' && $scope.arrowPosition) {
				arrowPosition = $scope.arrowPosition.split('-');
				var width = parseInt($scope.width) || $el.width();

				$timeout(calculatePosition);

				// if tooltip is ngShown, recalculate position when it is shown
				if ($attrs.ngShow) {
					$scope.$watch($attrs.ngShow, function(newValue) {
						if (newValue) {
							// recalculate position
							calculatePosition();
						}
					});
				}
			}

			function calculatePosition() {
				var arrowDistance = 29;
				var arrowHeight = 8;
				var arrowBase = 12;

				var styleObj = {
					position: 'absolute',
					margin: 0
				};

				var $target = $('#' + $scope.targetElId);
				var position = $target.position();
				var top = position.top;
				var left = position.left;
				var offset = $target.offset();
				var height = getTooltipHeight($el);
				width = Number($scope.width) || $el.width();
				switch(arrowPosition[0]) {
					case 'top':
						top += $target.height() + arrowHeight;
						break;
					case 'bottom':
						top -= (height + arrowHeight);
						break;
					case 'left':
						left += $target.width() + arrowHeight;
						break;
					case 'right':
						left -= width + arrowBase;
						break;
				}
				switch(arrowPosition[1]) {
					case 'left':
						left -= (arrowDistance + (arrowBase/2) - ($target.width()/2));
						break;
					case 'right':
						left -= (width - arrowDistance - (arrowBase/2) - ($target.width()/2));
						break;
					case 'top':
						top -= (arrowDistance + (arrowBase/2) - ($target.height()/2));
						break;
					case 'bottom':
						top -= (height - arrowDistance - (arrowBase/2) - ($target.height()/2));
						break;
					case 'center':
						if (arrowPosition[0] === 'top' || arrowPosition[0] === 'bottom') {
							left -= width/2 - $target.width()/2;
						} else {
							top -= height/2 - $target.height()/2;
						}
						break;
				}

				// adjust if tooltip horizontal position exceeds the screen
				if (offset.left - $el.width()/2 < 0) {
					left -= offset.left - $el.width()/2;
				} else if (offset.left + $el.width()/2 > window.innerWidth - 10) {
					left += (window.innerWidth - 10) - (offset.left + $el.width()/2);
				}

				position.top = top;
				position.left = left;
				angular.extend(styleObj, position);

				// Set the styles.
				$el.css(styleObj);
			}

			function getTooltipHeight($element) {
				var previousCss  = $element.attr("style");
				$element
				    .css({
				        position:   'absolute', // Optional if #myDiv is already absolute
				        visibility: 'hidden',
				        display:    'block'
				    });
				if ($element.hasClass('ng-hide')) {
					$element.removeClass('ng-hide');
					var wasNgHidden = true;
				}
				var optionHeight = $element.find('.ha-tooltip').outerHeight();
				$element.attr("style", previousCss ? previousCss : "");
				if (wasNgHidden) {
					$element.addClass('ng-hide');
				}
				return optionHeight;
			}

			$scope.$emit('$haTooltipReady');
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaTooltipLink,
			controller: HaTooltipController,
			transclude: true,
			templateUrl: haConfig.getTemplateUrl('ha-tooltip-base-template.html')
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Avatar Directive
	// --------------------------------------------
	//
	// * **Class:** HaAvatar
	// * **Author:** Cory Shaw
	//
	// Basic template for rendering avatars

	'use strict';

	var module = angular.module('haAvatarModule', []);

	module.directive('haAvatar', ['haConfig', function (haConfig) {

		var HaAvatarController = function () {

		};

		HaAvatarController.$inject = ['$scope'];

		var HaAvatarLink = function ($scope, $elem, $attrs) {

			$attrs.$observe('size', function () {
				$scope.setAvatarSize();
			});

			$scope.name = $attrs.name;

			$attrs.$observe('name', function () {
				$scope.name = $attrs.name;
			});

			$scope.alt = $attrs.alt;

			$scope.src = $attrs.src;

			$attrs.$observe('src', function () {
				$scope.src = $attrs.src;
			});

			$scope.initials = $attrs.initials;

			$attrs.$observe('initials', function () {
				$scope.initials = $attrs.initials;
			});

			$scope.seatSelection = $attrs.seatSelection;

			$attrs.$observe('seatSelection', function () {
				$scope.seatSelection = $attrs.seatSelection;
			});

			$scope.setAvatarSize = function () {
				$scope.size = $attrs.size;

				if (!$scope.size) {
					$scope.size = 'large';
				}

				if ($scope.size === 'small') {
					$scope.avatarWidth = 70;
					$scope.avatarHeight = 70;
				}

				if ($scope.size === 'large') {
					$scope.avatarWidth = 125;
					$scope.avatarHeight = 125;
				}
			};

			$scope.setAvatarSize();

			$scope.$emit('$haAvatarReady');
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaAvatarLink,
			controller: HaAvatarController,
			templateUrl: haConfig.getTemplateUrl('ha-avatar-base-template.html')
		};
	}]);

})(angular);;
(function (angular) {

	// Ha Dynamic Modal Directive
	// --------------------------------------------
	//
	// * **Class:** HaDynamicModal
	// * **Author:** Cory Shaw
	//
	// Modal directive to be used in cases where it's content is dynamic and needs to receive/send data to the parent page

	'use strict';

	var module = angular.module('haDynamicModalModule', []);

	module.directive('haDynamicModal', function () {

		var HaDynamicModalController = function ($scope, $document, $timeout) {

			$scope.body = angular.element(document).find('body');
			$scope.backdropEl = angular.element('<div class="modal-backdrop" ng-click="$modalCancel()">');

			$scope.handleEscPressed = function (event) {
				if (event.keyCode === 27) {
					$scope.$modalCancel();
				}
			};

			$scope.closeFn = function () {
				$scope.body.unbind('keydown', $scope.handleEscPressed);
				$scope.backdropEl.removeClass('fade in');
				//loadingSpinner.remove();
				$scope.modalEl.removeClass('in');
				$timeout(function () {
					$scope.body.removeClass('modal-active');
				}, 300);
			};

			$scope.body.bind('keydown', $scope.handleEscPressed);
		};

		HaDynamicModalController.$inject = ['$scope'];

		var HaDynamicModalLink = function ($scope) {

			$scope.exampleMethod = function () {
				return $scope;
			};

			$scope.$emit('$haDynamicModalReady');
		};

		return {
			restrict: 'A',
			scope: true,
			transclude: true,
			link: HaDynamicModalLink,
			controller: HaDynamicModalController
		};
	});

})(angular);
;
(function (angular) {

	// Ha Alert Directive
	// --------------------------------------------
	//
	// * **Class:** HaAlert
	// * **Author:** Cory Shaw
	//
	// Injects Alert html and styling where used

	'use strict';

	var module = angular.module('haAlertModule', []);

	module.directive('haAlert', ['$window', 'haConfig', function ($window, haConfig) {

		var HaAlertController = function ($scope) {

			$scope.showAlert = true;

			$scope.$on('$showAlert', function (event, alertId) {
				if (alertId === $scope.alertId) {
					$scope.showAlert = true;
				}
			});

			$scope.$on('$hideAlert', function () {
				$scope.showAlert = false;
			});

			$scope.hideAlert = function () {
				$scope.showAlert = false;
				$scope.$emit('haAlertClosed', $scope.alertId);
			};

			$scope.postTealeaf = function () {
				if ($window.TLT != null) {
					$window.TLT.logCustomEvent('HAErrorMessage', {
						ErrorHeader: $scope.header,
						ErrorMessage: $scope.description
					});
				}
			};
		};

		HaAlertController.$inject = ['$scope'];

		var HaAlertLink = function ($scope, $el, $attrs) {

			$scope.type = $attrs.type;
			$scope.alertId = $attrs.alertId;

			$scope.noClose = $attrs.noClose !== undefined;

			$scope.noIcon = !!$attrs.noicon;

			$scope.header = $attrs.header;
			$scope.description = $attrs.description;
			$scope.customIconClass = $attrs.customIconClass;

			$attrs.$observe('header', function () {
				$scope.header = $attrs.header;
				$scope.showAlert = true;
			});

			$attrs.$observe('type', function () {
				$scope.type = $attrs.type;
				$scope.showAlert = true;
				if ($scope.type === 'error') {
					$scope.noClose = true;
				}
			});

			$attrs.$observe('description', function () {
				$scope.description = $attrs.description;
			});

			$attrs.$observe('noicon', function () {
				$scope.noIcon = !!$attrs.noicon;
				$scope.showAlert = true;
			});

			$attrs.$observe('customIconClass', function () {
				$scope.customIconClass = $attrs.customIconClass;
			});

			if (typeof $el.context !== 'undefined' && $scope.header !== '') {
				if (typeof ($el.context.offsetHeight !== undefined) && $el.context.offsetHeight !== 0) {

					// if alert has transcluded material - grab text - append <p class="sr-only">text content</p> for screen readibility
					if ($el.find('.alert-content [ng-transclude]').children().length > 0) {
						var srText = $el.find('.alert-content [ng-transclude]').text().trim();
						$el.find('.alert-hidden-content.sr-only').append('<p class="sr-only">' + srText + '</p>');
					}

					if ((['promo', 'info'].indexOf($attrs.type.toLowerCase()) === -1) && ($attrs.disableAutofocus !== "true")) { // ignore autofocus for the following alert types: promo, info
						$el.find('[role="alert"]').focus();
					}

					if ($scope.description !== undefined) {
						$scope.postTealeaf();
					}
				}
			}
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaAlertLink,
			transclude: true,
			templateUrl: haConfig.getTemplateUrl('ha-alert-base-template.html'),
			controller: HaAlertController
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Help And Tips Directive
	// --------------------------------------------
	//
	// * **Class:** HaHelpAndTips
	// * **Author:** George Pantazis
	//
	// Widget for displaying information about a destination city in a given month.

	'use strict';

	var module = angular.module('haHelpAndTipsModule', []);

	module.directive('haHelpAndTips', ['haConfig', function (haConfig) {

		var HaHelpAndTipsController = function ($scope, haHelpAndTips) {

			// Month is 1-based (Jan=1, Dec=12).
			$scope.getData = function (city, month) {
				haHelpAndTips.getData(city, function (data) {
					$scope.cityName = data.cityName;
					$scope.month = data.months[(month - 1) % 12];
					$scope.monthDate = new Date(2000, month - 1);
					$scope.$emit('$haHelpAndTips:dataLoaded');
				});
			};
		};

		HaHelpAndTipsController.$inject = ['$scope', 'haHelpAndTips'];

		var HaHelpAndTipsLink = function ($scope, $el, $attrs) {

			$attrs.$observe('city', function () {
				$scope.getData($attrs.city, $attrs.month);
			});

			$scope.$on('$haHelpAndTips:dataLoaded', function () {
				$scope.$emit('$haHelpAndTips:Ready');
			});
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaHelpAndTipsLink,
			templateUrl: haConfig.getTemplateUrl('ha-help-and-tips-base-template.html'),
			controller: HaHelpAndTipsController
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Recent Searches Directive
	// --------------------------------------------
	//
	// * **Class:** HaRecentSearches
	// * **Author:** George Pantazis
	//
	// Allows quick access to the user's most recent searches, which, when selected, should overwrite the current search.

	'use strict';

	var module = angular.module('haRecentSearchesModule', []);

	module.directive('haRecentSearches', ['haConfig', function (haConfig) {

		var HaRecentSearchesController = function ($scope, haSearchCache) {

			$scope.restoreSearch = function (which) {
				haSearchCache.restoreSearch(which);
				$scope.deactivate();
			};

			$scope.activate = function () {
				$scope.active = true;
				return $scope;
			};

			$scope.deactivate = function () {
				$scope.active = false;
				return $scope;
			};

			$scope.active = false;
		};

		HaRecentSearchesController.$inject = ['$scope', 'haSearchCache', 'haCustomerSelections'];

		var HaRecentSearchesLink = function ($scope, $el) {

			$scope.toggleActive = function () {
				if ($scope.active) {
					$scope.deactivate();
				} else {
					$el.find('.toggle').focus();
					$scope.activate();
				}
				return $scope;
			};

			// Used to prevent loss of focus where relevant.
			$scope.preventDefault = function ($event) {
				$event.preventDefault();
			};

			$scope.$emit('$haRecentSearchesReady');
		};

		return {
			restrict: 'A',
			scope: true,
			templateUrl: haConfig.getTemplateUrl('ha-recent-searches-base-template.html'),
			link: HaRecentSearchesLink,
			controller: HaRecentSearchesController
		};
	}]);

})(angular);
;
(function (angular) {

	/*jshint strict:false */

	// Ha Location Input Directive
	// --------------------------------------------
	//
	// * **Class:** HaLocationInput
	// * **Author:** George Pantazis
	//
	// Search and select possible origins or destinations.

	// 'use strict'; is removed here due to a bug in Safari 6, iOS webkit.
	// https://github.com/jrburke/requirejs/issues/392

	var module = angular.module('haLocationInputModule',
		['haUtilsModule', 'haTemplateCache', 'haCitiesAPI', 'ui.bootstrap.typeahead', 'haAdvanceToNextInputService']);

	module.directive('haLocationInput', ['$compile', '$timeout', 'haTemplateCache', 'haConfig',
		function ($compile, $timeout, haTemplateCache, haConfig) {

			// Pre-Load template contents on initial load to the templateCache for faster rendering of modal popup
			$timeout(function () {
				haTemplateCache.get(haConfig.getRazorTemplateUrl('destination-modal-hawaii'));
				haTemplateCache.get(haConfig.getRazorTemplateUrl('destination-modal-na'));
				haTemplateCache.get(haConfig.getRazorTemplateUrl('destination-modal-asia'));
				//haTemplateCache.get(haConfig.getRazorTemplateUrl('destination-modal-everywhere'));
				haTemplateCache.get(haConfig.getRazorTemplateUrl('destination-modal-base'));
			}, 1000);

			var link = function ($scope, $el, $attrs) {
				var scope = $scope.haLocationInput = $scope.haLocationInput || {};

				// Bindings
				// --------

				$attrs.$observe('required', function (newVal) {
					$scope.required = newVal;
				});

				// Scope Variable Assignments
				// --------------------------

				// NEP: Because 'errorMessage is used the ha-input parent scope. Wrong, I know.'
				$scope.errorMessage = $attrs.errorMessage;

				scope.placeholder = $attrs.placeholder;
				scope.label = $attrs.label;
				scope.required = $attrs.required;
				scope.tagname = $attrs.tagname;
				scope.tagPrefix = $attrs.tagPrefix !== undefined ? $attrs.tagPrefix : '';
				scope.tagname2 = $attrs.tagname2;  //for RT query (additional Parameter)
				scope.tagPrefix2 = $attrs.tagPrefix2 !== undefined ? $attrs.tagPrefix2 : '';  //for RT query (additional Parameter)
				scope.disabledInput = $attrs.disabledInput === 'true';
				scope.selectedCity = scope.selectedCity || $attrs.haLocationSelectedCity;
			};

			return {
				restrict: 'A',
				scope: {
					preset: '=',
					disabledInput: '=',
					originLeg: '=',
					destinationLeg: '=',
					cityFilterIndex: '=',
					cityFilterIsDeparture: '='
				},
				link: link,
				templateUrl: haConfig.getRazorTemplateUrl('ha-location-input-base-template'),
				controller: 'haLocationInputController'
			};
		}
	]);

	module.controller('haLocationInputController', ['$scope', '$log', 'haCitiesAPI',
		function ($scope, $log, haCitiesAPI) {
			var scope = $scope.haLocationInput = $scope.haLocationInput || {};

			scope.CityList = [];
			scope.preset = $scope.preset;
			scope.disabled = $scope.disabledInput;
			scope.originLeg = $scope.originLeg;
			scope.destinationLeg = $scope.destinationLeg;
			scope.cityFilterIndex = $scope.cityFilterIndex;
			scope.cityFilterIsDeparture = !!$scope.cityFilterIsDeparture;

			function init() {
				if ($scope.$root.DefaultCity != null && !scope.selectedCity) {
					scope.selectedCity = $scope.$root.DefaultCity;
				}

				var handleCityList = function (cityList) {
					$log.debug('SEND: haLocationInput:ready:CityList');
					scope.CityList = cityList;
					$scope.$broadcast('haLocationInput:ready:CityList');
				};

				if (scope.cityFilterIndex != null) {
					haCitiesAPI.getFilteredCityList(scope.cityFilterIndex, scope.cityFilterIsDeparture)
					.then(handleCityList);
				} else {
					haCitiesAPI.getCityList()
					.then(handleCityList);
				}
			}

			init();
		}]);
})(angular);
;
(function (angular) {
	'use strict';

	var module = angular.module('haLocationInputModule');

	module.directive('haLocationTypeahead', [
		'$compile', '$log', '$filter', '$timeout', 'haAdvanceToNextInput', 'haConfig',
		function ($compile, $log, $filter, $timeout, haAdvanceTo, haConfig) {
			var link = function ($scope, $element /*, $attrs */) {
				var scope = $scope.haLocationData;

				// Directive link function sets up the twitter typeahead component for location
				var onFocus = function (/* $event */) {
					$scope.$apply(function () {
						scope.focused = true;

						$scope.$emit('HaLocationInput:focused', $element);

						if ($scope.$root.DefaultCity != null && !scope.selectedCity) {
							scope.selectedCity = $scope.$root.DefaultCity;
						}

						// $element.find('input').not('.tt-hint').select();
					});
				};

				var onBlur = function ($event) {
					$timeout(function () {
						// $scope.select($scope.activeIdx);

						scope.focused = false;

						if ($event != null) {
							var input = $event.target;
							if ((input != null) && (input.value == null) || (input.value === '')) {
								$scope.clearSelection();
							}
						}

						$scope.$emit('HaLocationInput:blurred');
					}, 350);
				};

				$element.find('input[typeahead]')
				.on('focus', onFocus)
				.on('blur', onBlur);

				var initSelection = function () {
					if ((scope.CityList == null) || (scope.CityList.length === 0)) {
						return;
					}

					$log.debug('haLocationTypeahead._onCityListReady');

					var match;
					var filter = $filter('filter');
					// Pre-select city by code
					if (scope.preset) {
						match = filter(scope.CityList, {Code: scope.preset});
						if (match.length > 0) {
							$log.debug('haLocationTypeahead: Matched "preset" (' + scope.preset + ').');
							$scope.processSelection(match[0]);
						} else {
							$log.debug('haLocationTypeahead: Could not set "preset" (' + scope.preset + '). No match in CityList.');
						}
					} else {
						var code;
						if ((scope.originLeg != null) && (scope.originLeg.origin != null)) {
							code = scope.originLeg.origin;
						} else if ((scope.destinationLeg != null) && (scope.destinationLeg.destination != null)) {
							code = scope.destinationLeg.destination;
						}
						if (code != null) {
							match = filter(scope.CityList, {Code: code});
							if (match.length > 0) {
								$log.debug('haLocationTypeahead: Matched "code" (' + code + ').');
								$scope.processSelection(match[0]);
							} else {
								$log.debug('haLocationTypeahead: Could not set "code" (' + code + '). No match in CityList.');
							}
						} else {
							$log.debug('haLocationTypeahead: No legs specified.');
						}
					}
				};

				$log.debug('ON: haLocationInput:ready:CityList');
				$scope.$on('haLocationInput:ready:CityList', function () {
					$log.debug('RCVD: haLocationInput:ready:CityList');
					initSelection();
				});

				$log.debug('ON: $root.selections.legs:ready');
				$scope.$on('$root.selections.legs:ready', function () {
					$log.debug('RCVD: $root.selections.legs:ready');
					initSelection();
				});

				initSelection();

				// NEP: Hack to allow pre-filled fields to match in the typeahead
				$scope.$watch('currentCityDisplayName', function (newVal) {
					if (newVal === undefined) {
						return;
					}

					var inputScope = $element.parents('[ha-input]').scope();
					if ((inputScope != null) && (inputScope.$model != null)) {
						inputScope.$model.$setViewValue(newVal);
						inputScope.$model.$render();
					}
				});

				$scope.advanceToNextInput = function () {
					haAdvanceTo.next($element);
				};

			};

			return {
				restrict: 'AE',
				link: link,
				scope: {
					haLocationData: '=haLocationData'
				},
				replace: true,
				controller: 'haLocationTypeaheadController',
				templateUrl: haConfig.getRazorTemplateUrl('ha-location-typeahead')
			};
		}]);

	module.controller('haLocationTypeaheadController', ['$scope', '$timeout', 'haUtils', function ($scope, $timeout, haUtils) {
		var scope = $scope.haLocationData;

		scope.focused = false;

		// Make token pairs to handle searches like "San Fr"
		var makeTokenPairs = function (tokens) {
			var pairs = [];

			for (var i = 0; i < tokens.length - 1; i++) {
				pairs.push(tokens[i] + ' ' + tokens[i + 1]);  // Pair with space
				pairs.push(tokens[i] + tokens[i + 1]);        // Pair without space
			}

			return tokens.concat(pairs);
		};

		$scope.haAirportSort = function ($viewValue) {
			return function (city) {
				var sum = 0;

				if ($viewValue == null) {
					return sum;
				}

				// Perfect match
				if (city.DisplayName === $viewValue) {
					sum = 100;
				}

				var concatValue = $viewValue.split(' ').join('');

				if ((city.Code != null) && (city.Code.toUpperCase() === $viewValue.toUpperCase())) {
					sum += 1;
				}

				if (city.DisplayName != null) {
					var displayNameTokens = makeTokenPairs(city.DisplayName.split(' '));

					angular.forEach(displayNameTokens, function (token) {
						if (token.toLowerCase().indexOf($viewValue.toLowerCase()) === 0) {
							sum += 0.3;
						} else if (token.toLowerCase().indexOf(concatValue.toLowerCase()) === 0) {
							sum += 0.15;
						}
					});
				}

				if (city.LinkedAirportCodes != null) {
					var linkedAirportCodes = city.LinkedAirportCodes.split(',');

					angular.forEach(linkedAirportCodes, function (code) {
						if (code.toUpperCase() === $viewValue.toUpperCase()) {
							sum += 0.2;
						}
					});
				}

				if (city.SearchTags != null) {
					var searchTagTokens = makeTokenPairs(city.SearchTags.split(' '));

					angular.forEach(searchTagTokens, function (token) {
						if ((token.toLowerCase().indexOf($viewValue.toLowerCase()) === 0) || (token.toLowerCase().indexOf(concatValue.toLowerCase()) === 0)) {
							sum += 0.1;
						}
					});
				}

				// Only boost weight for HA Cities that aleady match!
				if ((sum > 0) && (city.IsHACity)) {
					sum += 0.5;
				}

				city._weight = sum; // jscs:ignore disallowDanglingUnderscores

				return -sum;    // correct sort order
			};
		};

		$scope.haAirportFilter = function (city) {
			return city._weight > 0; // jscs:ignore disallowDanglingUnderscores
		};

		// All Asian langs match on first 2 chars, English (default) match on first 3 chars.
		var isAsianLang = haUtils.isJP() || haUtils.isKR() || haUtils.isCN() || haUtils.isTW();
		$scope.typeaheadMinLength = function () {
			return (isAsianLang) ? 2 : 3;
		};

		var setLegs = function (cityData) {
			if (scope.originLeg != null) {
				scope.originLeg.origin = cityData.Code;
				scope.originLeg.originCityData = cityData;
			}
			if (scope.destinationLeg != null) {
				scope.destinationLeg.destination = cityData.Code;
				scope.destinationLeg.destinationCityData = cityData;
			}
		};

		$scope.processSelection = function (cityData) {
			$scope.$root.DefaultCity = null;
			$scope.currentCityDisplayName = cityData.DisplayName;

			scope.selectedCity = cityData;
			scope.currentCityCode = cityData.Code;
			scope.linkedCityCode = cityData.LinkedAirportCodes;
			setLegs(cityData);
		};

		$scope.clearSelection = function () {
			scope.currentCityCode = null;
			scope.linkedCityCode = null;
		};

		$scope.onSelected = function ($item) {
			$timeout(function () {
				scope.focused = false;
				// $scope.$item = cityData;
				// $scope.$model = cityData.DisplayName;
				// $scope.$label = cityData.DisplayName;

				$scope.processSelection($item);
				$scope.advanceToNextInput();
			}, 200);
		};

	}]);
}(angular));
;
(function (angular) {
	'use strict';

	var module = angular.module('haLocationInputModule');

	module.directive('haLocationModal', ['haModal', 'haCitiesSvc', 'haConfig', '$timeout', function (haModal, haCitiesSvc, haConfig, $timeout) {
		function link($scope, $element, $attrs) {
			// Directive link function handles wiring up to specified event name and launching model dialog

			var eventName = $attrs.eventName || 'click';
			$scope.filterName = $attrs.filterFn !== '' ? $attrs.filterFn : null;
			$scope.cityListType = $attrs.cityListType || 'normal';

			var onOpen = function ($event) {
				$scope.updateAvailableCities($scope.region);

				var sender = $event.currentTarget;

				// Open modal dialog
				haModal(haConfig.getRazorTemplateUrl('destination-modal-base'), {
					id: 'locationModal',
					backdrop: 'true',
					scope: $scope,
					cancel: {
						fn: function () {
							$timeout(function () {
								$(sender).prevAll('[ha-typeahead]').focus();
							}, 300);
						}
					}
				});

			};

			$element.on(eventName, onOpen);

			// use different source for city list based on cityListType
			if ($scope.cityListType.match('^reshop')) {
				var reshopParam = $scope.cityListType.split('-');
				if (reshopParam.length === 3) {
					var leg = reshopParam[1];
					var isOrigin = reshopParam[2] === 'origin';
					haCitiesSvc.getFilteredCities(leg, isOrigin).then(function (data) {
						$scope.cities = data;
					});
				}
			} else if ($scope.cityListType.match('^expertbooking')) {
				haCitiesSvc.getAllCitiesExpertBooking().then(function (data) {
					$scope.cities = data;
				});
			} else {
				haCitiesSvc.getAllCities().then(function (data) {
					$scope.cities = data;
				});
			}
		}

		return {
			restrict: 'A',
			link: link,
			controller: 'haLocationModalController',
			scope: {
				region: '@haLocationModal',
				selectedCity: '=haLocationSelectedCity'
			}
		};
	}]);

	module.controller('haLocationModalController', ['$scope', '$filter', function ($scope, $filter) {
		// Initializes the controller
		$scope.regionalCities = [];

		function getMarketCode(region) {
			// Converts a specified market code into its well known numeric value

			var marketCode = 0;

			switch (region) {
				case 'hawaii':
					marketCode = 1;
					break;
				case 'na':
					marketCode = 2;
					break;
				case 'asia':
					marketCode = 3;
					break;
			}

			return marketCode;
		}

		function calculateColumn(region) {
			switch (region) {
				case 'hawaii':
					if ($scope.$root.isMobile) buildColumnList($scope.regionalCities, 'IslandName', 1);
					break;
				case 'na':
					buildColumnList($scope.regionalCities, 'State', 4);
					break;
				case 'asia':
					buildColumnList($scope.regionalCities, 'CountryName', 4);
					break;
			}
		}

		function buildColumnList(cityList, groupFieldName, columnCount) {
			var count = 0;
			var columnContent = [];
			$scope.regionalCities = [];
			cityList = $filter('citiesOrderBy')(cityList, groupFieldName, false);
			var itemCount = cityList.length;
			cityList = _.groupBy(cityList, groupFieldName);
			var average = Math.round((itemCount + Object.keys(cityList).length) / columnCount);

			angular.forEach(cityList, function (group, key) {
				group = $filter('citiesOrderBy')(group, 'NonLocalisedCityName', false);
				if (count + group.length > average * 1.5 ) {
					// if not much slots left, just continue to next column
					if (average - count < average / 3) {
						$scope.regionalCities.push(columnContent);
						count = 0;
						columnContent =[];
					}
					// break group if too long
					if (group.length > average * 1.5) {
						var cut = average - count + 3;
						columnContent.push({ Name: key, Cities: group.slice(0, cut)});
						$scope.regionalCities.push(columnContent);
						columnContent = [];
						columnContent.push({ Name: key, Cities: group.slice(cut)});
						count = group.length - cut;
					} else {
						columnContent.push({ Name: key, Cities: group});
						count = group.length + 1;
					}
				} else {
					count += group.length + 1;
					columnContent.push({ Name: key, Cities: group});
				}
				if (count >= average && $scope.regionalCities.length < columnCount - 1) {
					count = 0;
					$scope.regionalCities.push(columnContent);
					columnContent = [];
				}
			});
			
			// Sort islands according to priority assigned in Sitecore
			if (groupFieldName === 'IslandName') {
				angular.forEach(columnContent, function(group) {
					group.islandSortOrder = !!group.Cities ? group.Cities[0].IslandSortOrder : 9;
				});
			}

			$scope.regionalCities.push(columnContent);
		}

		$scope.$watch('region', function (current, old) {
			if (current === old) {
				return;
			}

			$scope.updateAvailableCities(current);
		});

		$scope.selectCity = function (city) {
			// Handles selection of a new city, called from view

			// HACK!
			var parent;
			do {
				parent = $scope.$parent;
				if (parent.onSelected != null) {
					parent.onSelected(city);
					break;
				}
			} while (parent != null);
			$scope.$modalClose(); // Added to scope from haModelService
		};

		$scope.selectRegion = function (region) {
			// Handles selection of a new region (tab), called from view
			$scope.region = region;
		};

		$scope.isRegion = function (region) {
			return this.region === region;
		};

		$scope.getCitiesByMarket = function (marketId) {
			// Filters the available cities by the specified marketId

			if (!$scope.cities) {
				return [];
			}

			if ($scope.filterName === 'isHACity') {
				return $scope.cities.filter(function (city) {
					return city.Market === marketId && city.IsHACity === true;
				});

			} else {
				return $scope.cities.filter(function (city) {
					return city.Market === marketId;
				});
			}
		};

		$scope.regionHasCities = function(region) {
			var marketCode = getMarketCode(region);
			var cities = $scope.getCitiesByMarket(marketCode);
			return !!cities.length;
		}

		$scope.updateAvailableCities = function (region) {
			// Updates the available regional cities by filtering by the specified region
			// ex) hawaii, na, asia

			var marketCode = getMarketCode(region);
			$scope.regionalCities = $scope.getCitiesByMarket(marketCode);
			calculateColumn(region);
		};

	}]);

	module.filter('citiesOrderBy', [function () {
	    return function (items, field, reverse) {
	        var filtered = [];
	        angular.forEach(items, function (item) {
	            filtered.push(item);
	        });
	        filtered.sort(function (a, b) {
	            return (a[field] > b[field] ? 1 : -1);
	        });
	        if (reverse) filtered.reverse();
	        return filtered;
		};
	}]);

}(angular));
;
(function (angular) {

	// Ha Date Input Directive
	// --------------------------------------------
	//
	// * **Class:** HaDateInput
	// * **Author:** George Pantazis
	//
	// Validated date input for a given flight leg.

	'use strict';

	var module = angular.module('haDateInputModule', ['haAdvanceToNextInputService']);

	module.directive('haDateInput', ['haUtils', 'haConfig', 'haAdvanceToNextInput', function (haUtils, haConfig, haAdvanceTo) {

		var HaDateInputController = function ($scope, haCustomerSelections) {

			$scope.updateTimestamp = function (date) {

				if (date) {
					$scope.date = haUtils.leftPad(date.month, 2) + haUtils.leftPad(date.day, 2) + date.year.toString();
					$scope.timestamp = new Date(date.year, date.month - 1, date.day);
					$scope.updated = date.updated;
				} else {
					$scope.date = $scope.timestamp = undefined;
				}
			};

			$scope.setDate = haCustomerSelections.setLegDate;

			$scope.getLegData = function (which) {
				return haCustomerSelections.getLeg(which);
			};
		};

		HaDateInputController.$inject = ['$scope', 'haCustomerSelections'];

		var HaDateInputLink = function ($scope, $el, $attrs) {

			$scope.onFocus = function () {
				$el.addClass('focused');
				$scope.$emit('HaDateInput:focused', $el);
			};

			$scope.onBlur = function () {
				$el.removeClass('focused');
				$scope.$emit('HaDateInput:selected', $el);
			};


			$scope.advanceToNextInput = function () {
				haAdvanceTo.next($el);
			};


			// $scope.autoprogress = function () {
			//     if (!$scope.$root.$progressionTimeout) {

			//         $scope.$root.$progressionTimeout = setTimeout(function () {

			//             delete $scope.$root.$progressionTimeout;

			//             var $selectable = $('[ha-progress-on-select] :tabbable'),
			//               currentIndex = $selectable.index($el.find(':tabbable')),
			//               $nextElement = $selectable.eq(currentIndex + 1);

			//             if ($nextElement.length > 0 && currentIndex + 1 > 0) {
			//                 $nextElement.focus();
			//                 return;
			//             }

			//             $scope.$emit('HaDateInput:selected', $el);

			//         }, 0);
			//     }
			// };

			$scope.legData = $scope.getLegData($attrs.targetLeg);

			$scope.$watch('$root.selections.legs', function () {

				var previousUpdate = $scope.updated;

				$scope.legData = $scope.getLegData($attrs.targetLeg);

				$scope.updateTimestamp($scope.legData.date);

				if ($scope.updated !== previousUpdate) {
					$scope.advanceToNextInput();
				}

			}, true);

			$scope.$watch('date', function (nv, ov) {
				if (nv === ov) {
					return;
				}
				if ($scope.date && $scope.legData) {
					$scope.setDate($attrs.targetLeg, {
						month: $scope.date.substr(0, 2),
						day: $scope.date.substr(2, 2),
						year: $scope.date.substr(4, 4)
					});
				}

				// setTimeout(function() {

				//   var $selectable = $('[ha-progress-on-select] :tabbable'),
				//     currentIndex = $selectable.index($el.find(':tabbable')),
				//     $nextElement = $selectable.eq(currentIndex + 1);

				//   if ($nextElement.length > 0 && currentIndex + 1 > 0) {
				//     $nextElement.focus();
				//     return;
				//   }

				//   $scope.$emit('HaDateInput:selected', $el);

				// }, 0);

			});

			$scope.label = $attrs.label;

			$scope.tagname = $attrs.tagname;
			$scope.tagPrefix = $attrs.tagPrefix !== undefined ? $attrs.tagPrefix : '';
			$scope.placeholder = $attrs.placeholder;
			$scope.required = $attrs.required;
			$scope.errorMessage = $attrs.errorMessage;
			$scope.$emit('$haDateInputReady');
		};

		return {
			restrict: 'A',
			scope: true,
			templateUrl: haConfig.getTemplateUrl('ha-date-input-base-template.html'),
			link: HaDateInputLink,
			controller: HaDateInputController
		};
	}]);

	module.directive('haDateFormatter', ['$filter', '$locale', function ($filter, $locale) {
		// Wrap 'shortDate' format to allow customization
		function shortDate() {
			// if (!haUtils.isEN()) {
			return $locale.DATETIME_FORMATS.shortDate;
			// } else {
			//     return 'M/d/yyyy';
			// }
		}

		return {
			require: 'ngModel',
			link: function (scope, element, attrs, ngModelCtrl) {
				ngModelCtrl.$formatters.push(function (value) {
					if (value == null || typeof value !== 'string') {
						return '';
					}

					var model = {
						month: parseInt(value.substr(0, 2), 10) - 1,    // Yeah, months are 0-11 in JS...
						day: parseInt(value.substr(2, 2), 10),
						year: parseInt(value.substr(4, 4), 10)
					};
					var date = new Date(model.year, model.month, model.day, 0, 0, 0, 0);
					return $filter('date')(date, shortDate());
				});
			}
		};
	}]);

	module.run(['$locale', 'haUtils', function ($locale, haUtils) {
		// Override the EN 'shortDate' to use 4-digit years
		if (haUtils.isEN()) {
			$locale.DATETIME_FORMATS.shortDate = 'M/d/yyyy';
		}
	}]);

})(angular);;
(function (angular) {

	// Share Widget Directive
	// --------------------------------------------
	//
	// * **Class:** ShareWidget
	// * **Author:** Cory Shaw
	//
	// Allows the user to share the current content on social networks.

	'use strict';

	var module = angular.module('shareWidgetModule', []);

	module.directive('shareWidget', ['haConfig', function (haConfig) {

		var ShareWidgetController = function ($scope) {
			$scope.shareServices = ['Twitter', 'LinkedIn', 'Facebook', 'Google+'];
		};

		var ShareWidgetLink = function ($scope) {
			$scope.$emit('$methodsBound');
		};

		ShareWidgetController.$inject = ['$scope'];

		return {
			restrict: 'A',
			scope: true,
			link: ShareWidgetLink,
			controller: ShareWidgetController,
			templateUrl: haConfig.getTemplateUrl('share-widget-base-template')
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Calendar Events Directive
	// --------------------------------------------
	//
	// * **Class:** HaCalendarEvents
	// * **Author:** George Pantazis
	//

	'use strict';

	var module = angular.module('haCalendarEventsModule', []);

	module.directive('haCalendarEvents', [function () {

		var HaCalendarEventsController = function ($scope, haCalendarEventsService) {

			$scope.getCalendarEvents = function (id, cb) {
				haCalendarEventsService.getEvents(id, function (data) {
					cb(data);
				});
			};
		};

		HaCalendarEventsController.$inject = ['$scope', 'haCalendarEventsService'];

		var HaCalendarEventsLink = function ($scope, $el, $attrs) {

			$scope.getCalendarEvents($attrs.haCalendarEvents, function (data) {
				$.extend($scope, data);
				$scope.$broadcast('$haCalendarEventsLoaded');
			});
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaCalendarEventsLink,
			controller: HaCalendarEventsController
		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	var module = angular.module('haCalendarModule', []);

	function YMD(date) {
		return date.format('YYYY-MM-DD');
	}

	function YM(date) {
		return date.format('YYYY-MM');
	}

	module.directive('haCalendar', ['$compile', '$locale', 'haConfig', 'haUnavailableDays', 'haAriaLiveService',
		function ($compile, $locale, haConfig, haUnavailableDays, ariaLiveSvc) {
			return {
				templateUrl: haConfig.getTemplateUrl('ha-calendar.html'),
				restrict: 'A',
				controller: ['$timeout','$rootScope', '$scope', '$element', '$attrs', '$parse', function ($timeout, $rootScope, scope, $el, attrs, $parse) {

					var options = $.extend(true, {
						calendars: 1,
						range_start: moment([1900]),
						range_end: moment([3000]),
                        double_wide: false,
						render: {
							calendar: function ($cal, isReturn) {

								if (options.rangeRender) {
									options.rangeRender($cal);
								}

                                if (options.double_wide) {
                                    $cal.addClass('double-wide');
                                }

								var idx = options.idx || 0;
								// if an airport pair is involved
								if (scope.legs && scope.legs[idx]) {
									var pair = getPairString(scope.legs[idx], isReturn);
									if (pair && pair.length) {
										haUnavailableDays.get(pair).then(function (data) {
											$cal.find('td[date]:not(.disabled)').each(function () {
												var $td = $(this);
												if (data.contains(moment($td.attr('date'), 'YYYY-MM-DD').toDate())) {
													$td.addClass('disabled');
												}
											});
										});
									}
									if (scope.tripType === 2) {
										$cal.addClass('double-wide');
									} else {
										$cal.removeClass('double-wide');
									}
								}
								// messages
								if (scope.messages) {
									var $wrap = $cal.closest('.datepicker');
									$wrap.find('header > .calendar-messages').html(scope.messages[idx]);
									ariaLiveSvc.updateMessage(scope.messages[idx]);
								}
								// optional onRender function
								if (options.onRender) {
									options.onRender($cal);
								}
							},
                            caption: function ($caption, date) {
                                var month = date.month();
                                var monthNames = $locale.DATETIME_FORMATS.MONTH;
                                var year = date.year();

                                $caption.html(monthNames[month] + ' ' + year);
                            },
							day: function ($td, date) {
								$td.html(date.date());
							}
						}
					}, scope.$eval(attrs.haCalendar));

					adjustNumCalendars(scope.tripType);

					scope.$on('trip type changed', function (event, data) {
						adjustNumCalendars(data);
						render();
					});

					scope.$on('airport changed', function () {
						render();
					});

					function adjustNumCalendars(tripType) {
						if ($rootScope.isMobile) {
							options.calendars = 1;
						} else if (typeof tripType === 'undefined') {
							return;
						} else if (tripType === 2) {
							options.calendars = 2;
						} else {
							options.calendars = 1;
						}
					}

					//get an airport pair string from a leg. e.g. "LAX-HNL"
					function getPairString(leg, reverse) {
						if (!leg || !leg.origin || !leg.origin.Code || !leg.destination || !leg.destination.Code) {
							return;
						}
						return reverse ? (leg.destination.Code + '-' + leg.origin.Code) : (leg.origin.Code + '-' + leg.destination.Code);
					}

					$el.children('header').children('h2').text(options.calendarHeading);

					var now = moment().startOf('day');
					var month_template = $el.find('li').remove()[0].outerHTML;
					var createMonth = $compile(month_template);
					var $month_list = $el.find('ol.months');

					if (options.hasOwnProperty("watch")) {
						scope.$watch(options.watch, render);
					}

					scope.$watch(function () {
						return options.range_start.valueOf();
					}, render);
					scope.$watch(function () {
						return options.range_end.valueOf();
					}, render);
					//The `viewing` date is the month of the leftmost calendar.
					//You can have a date `selected` out of view- or, for multi-calendar displays,
					//on a calendar that is visible but not within the leftmost one.
					scope.$watch(function () {
						return options.viewing.valueOf();
					}, render);

					var model_getter = $parse(attrs.haCalendarModel || 'date');
					var model = model_getter.bind(model_getter, scope);
					this.select = function (value) {
						$timeout(function() {
							model_getter.assign(scope, value);
						}, 0);
						scope.$emit('select', value);
					};

					scope.$watch(model, function (date) {

						$month_list.find('.selected').removeClass('selected');

						if (!date) {
							date = Math.max(new Date(), options.range_start.valueOf());
						}
						date = moment(date);
						if (!date.isValid() || !inRange(date)) {
							return;
						}

						// Force the viewer to display the currently selected month if sticky_viewer is set.
						if (options.sticky_viewer) {
							set_viewing(date);
						}

						var $li = $month_list.find('li[date=' + YM(date) + ']');
						if ($li.length) {
							return render();
						}
					});

					function get_viewing() {
						return options.viewing && moment(options.viewing).startOf('month');
					}

					function set_viewing(date) {
						options.viewing.year(date.year()).month(date.month());
					}

					this.render = render;
					function render() {
						var viewing = get_viewing();
						var month = moment(viewing);//create clone because we mutate it below
						$month_list.empty();
						//iterate over # of displayed months
						for (var i = 0; i < options.calendars; i++) {
							addMonthFor(month);
							month.add(1, 'months');
						}

						var selected = moment(model() || NaN);
						if (selected.isValid()) {
							$month_list.find('td[date=' + YMD(selected) + ']').addClass('selected');
						}
						options.render.calendar($el, options.isReturn);
					}

					scope.prev = function () {
						var date = dateOf('li:first').add(-1, 'month');
						set_viewing(date);
						scope.$emit('prev', date);
					};
					scope.next = function () {
						var date = dateOf('li:first');
						set_viewing(date.add(1, 'month'));
						scope.$emit('next', date);
					};

                    // scope.prev = this.prev;
                    // scope.next = this.next;

					/* not used
				function edgeMonth(date) {
					return options.range_start.isSame(date, 'month') || options.range_end.isSame(date, 'month');
					 } */

					this.inRange = inRange;
					function inRange(date) {
						return YMD(date).valueOf() >= YMD(options.range_start) && YMD(date).valueOf() < YMD(options.range_end);
					}

					this.dateOf = dateOf;
					function dateOf(selector) {
						return moment($month_list.find(selector).attr('date'), 'YYYY-MM-DD');
					}

					function addMonthFor(date) {
						var s = scope.$new(true);
						var first = moment(date).startOf('month');
						var days_in_month = moment(first).endOf('month').date();
						var offset = first.day();

						createMonth(s, function ($li) {
                            options.render.caption($li.find('caption'), first);
							$li
								.attr('date', YM(date))
								.find('tbody>tr>td')
								.each(function (i) {
									var day = i - offset + 1;
									if (i < offset || day > days_in_month) {
										return;
									}

									var date = moment(first).date(day);
									var diff = date.diff(now, 'days');
									var $this = $(this)
										.attr('date', YMD(date))
									.addClass(!diff ? ' today' : diff < 0 ? ' past' : ' future')
										.toggleClass('disabled', !inRange(date));

									options.render.day($this, date);
								});

							$month_list.append($li);
						});
					}

					// If selected is specified, it will render for that date. If unknown,
					// we default to showing today's month- with nothing selected.
					if (!options.viewing) {
						options.viewing = moment(model() || moment()).startOf('month');
					}
					scope.$emit('ready', this);
				}],

				compile: function ($el) {

					$el.find('th').each(function (i, th) {
						th.innerText = $locale.DATETIME_FORMATS.SHORTDAY[i];
					});

					return function (scope, $el, attrs, controller) {
						$el.find('ol.months').on('click', 'td[date]:not(.disabled)', function (e) {
							scope.$apply(function () {
								controller.select(controller.dateOf(e.target).toDate());
							});
						});
						$el.find('.prev').click(function () {
							scope.$apply(scope.prev);
						});
						$el.find('.next').click(function (e) {
							e.preventDefault();
							scope.$apply(scope.next);
						});
					};
				}
			};
		}]);

})(angular);
;
(function (angular) {
	'use strict';

	var module = angular.module('haCalendar2Module', []);

	module.directive('haCalendar2', ['haDateUtils', '$timeout', '$rootScope', '$parse', 'haConfig', function (haDateUtils, $timeout, $rootScope, $parse, haConfig) {
		return {
			templateUrl: haConfig.getTemplateUrl('ha-calendar2.html'),
			restrict: 'A',
			link: function (scope, el, attrs) {

				// Initialize stuff
				var departDate;
				var returnDate;
				if (attrs.mode) {
					scope.mode = attrs.mode;
				}
				if (attrs.monthsBackward) {
					scope.monthsBackward = parseInt(attrs.monthsBackward, 10);
				}
				if (attrs.monthsForward) {
					scope.monthsForward = parseInt(attrs.monthsForward, 10);
				}
				if (attrs.monthsToShow) {
					scope.monthsToShow = parseInt(attrs.monthsToShow, 10);
				}
				if (attrs.dayTemplate) {
					scope.dayTemplate = attrs.dayTemplate;
				}
				if (attrs.departDate) {
					departDate = $parse(attrs.departDate)(scope);
				}
				if (attrs.returnDate) {
					returnDate = $parse(attrs.returnDate)(scope);
				}
				if (attrs.getIsUnavailable) {
					scope.getIsUnavailable = $parse(attrs.getIsUnavailable)(scope);
				}

				if (attrs.departDate) {
					scope.$watch(attrs.departDate, function (value) {
						departDate = value;
					});
				}

				if (attrs.returnDate) {
					scope.$watch(attrs.returnDate, function (value) {
						returnDate = value;
					});
				}

				scope.mode = scope.mode || 'single';
				scope.monthsBackward = scope.monthsBackward || 0;
				scope.monthsForward = (typeof scope.monthsForward !== 'undefined') ? scope.monthsForward : 11;
				scope.monthsToShow = scope.monthsToShow || 1;
				scope.dayTemplate = scope.dayTemplate || 'ha-datepicker-day-template';
				scope.getIsUnavailable = scope.getIsUnavailable || function () {
						return false;
					};

				// Override months back and forward if future or past disabled
				scope.pastDisabled = typeof scope.pastDisabled !== 'undefined';
				scope.futureDisabled = typeof scope.futureDisabled !== 'undefined';
				scope.monthsBackward = (scope.pastDisabled) ? 0 : scope.monthsBackward;
				scope.monthsForward = (scope.futureDisabled) ? 0 : scope.monthsForward;

				var zero = '0';
				var halfsLeft = (scope.monthsToShow === 2) ? '-50%' : '-100%';
				var $ol = $('#months', el);
				var transitionEndEvent = transitionEnd();
				var animating = false;
				var dateToJumpTo;
				var singleDatePicker = scope.mode === 'single';

				scope.months = haDateUtils.getVisibleMonths(scope.monthsBackward, scope.monthsForward);
				scope.weekdays = scope.weekdays || haDateUtils.getDaysOfWeek();

				scope.limitIndex = (scope.monthsBackward > 0) ? scope.monthsBackward + 1 : scope.monthsToShow;
				scope.limitTrailer = -1 * scope.monthsToShow;

				if (singleDatePicker) {
					dateToJumpTo = scope.inputModel;
				} else if (departDate) {
					dateToJumpTo = departDate;
				} else if (!departDate && returnDate) {
					dateToJumpTo = returnDate;
				}

				if (dateToJumpTo && angular.isDate(dateToJumpTo)) {
					scope.limitIndex += moment(dateToJumpTo).diff(moment().startOf('month'), 'months');
				}

				$ol.on(transitionEndEvent, onTransitionEnd);

				scope.next = function (increment) {

					increment = increment || 1;

					if (animating || scope.limitIndex === (scope.monthsBackward + scope.monthsForward + 1)) {
						return;
					}

					scope.$emit('calendarForward');

					scope.limitIndex += increment;
					scope.limitTrailer = (scope.mode === 'stack') ? scope.limitTrailer -= increment : -1 * (scope.monthsToShow + 1);

					if (scope.mode !== 'stack') {
						$ol.addClass('animating');
						$timeout(function () {
							animating = true;
							$ol.css('left', halfsLeft);

							if (!transitionEndEvent) {
								onTransitionEnd();
							}
						});
					}
				};

				scope.prev = function () {

					if (animating || scope.limitIndex === scope.monthsToShow) {
						return;
					}

					scope.$emit('calendarBackward');

					scope.limitTrailer = (scope.mode === 'stack') ? scope.limitIndex * -1 : -1 * (scope.monthsToShow + 1);

					if (scope.mode !== 'stack') {
						$ol.css('left', halfsLeft);
						$timeout(function () {
							animating = true;
							$ol.addClass('animating');
							$ol.css('left', zero);

							if (!transitionEndEvent) {
								onTransitionEnd();
							}
						});
					}
				};

				scope.$on('calendarGoForward', function(event, data) {
					scope.next(data)
				});
				scope.$on('calendarGoBackward', scope.prev);

				scope.setDate = function (e, data) {
					$rootScope.$broadcast('setDate', new Date(data.day), e.target);
				};

				scope.displayDay = function (d, f, l) {
					return isEdgeDay(d, f, l) ? '-' : d.getDate();
				};

				scope.isEdgeDay = function (d, f, l, idx) {
					return isEdgeDay(d, f, l, idx);
				};

				scope.isDepart = function (date) {
					return (singleDatePicker) ? haDateUtils.isSameDay(scope.inputModel, date) : (departDate) ? haDateUtils.isSameDay(departDate, date) : false;
				};

				scope.isReturn = function (date) {
					return (singleDatePicker) ? haDateUtils.isSameDay(scope.inputModel, date) : (returnDate) ? haDateUtils.isSameDay(returnDate, date) : false;
				};

				scope.isSelected = function (date) {
					return !!departDate && !!returnDate && haDateUtils.isBetween(date, departDate, returnDate);
				};

				scope.isUnavailable = function (date) {
					return scope.getIsUnavailable(date) || pastFutureDisallowed(date);
				};

				scope.dayPlus = function (date, duration) {
					return (angular.isDate(date)) ? (new Date(+date)).dateAdd('day', duration) : new Date();
				};

				function pastFutureDisallowed(date) {
					var today = new Date();
					if (scope.pastDisabled || scope.futureDisabled) {
						return (scope.pastDisabled && (haDateUtils.isBefore(date, today) && !haDateUtils.isSameDay(date, today))) || (scope.futureDisabled && haDateUtils.isAfter(date, today));
					}
					return false;
				}

				function onTransitionEnd() {
					animating = false;
					$ol.removeClass('animating');

					scope.$apply(function () {
						scope.limitTrailer = -1 * scope.monthsToShow;
						if ($ol.css('left') === '0px') {
							scope.limitIndex--;
						} else {
							$ol.css('left', zero);
						}
					});
				}

				function transitionEnd() {
					var el = document.createElement('div');

					var transEndEventNames = {
						transition: 'transitionend',
						WebkitTransition: 'webkitTransitionEnd',
						MozTransition: 'transitionend',
						OTransition: 'oTransitionEnd otransitionend'
					};

					for (var name in transEndEventNames) {
						if (el.style[name] !== undefined) {
							return transEndEventNames[name];
						}
					}

					return false;
				}

				function isEdgeDay(d, f, l, idx) {
					return (f && d.getDate() > 7) || ((idx >= 4) && (d.getDate() >= 1 && d.getDate() < 15)); // TODO: Memoize
				}
			}
		};
	}]);

})(angular);
;
(function (angular) {

	// Hawaiian Carousel Directive
	// ===========================
	//
	// * **Class:** haCarousel
	// * **Author:** George Pantazis
	//
	// A directive for applying carousel behaviors to DOM wrappers.

	'use strict';

	var module = angular.module('haCarouselModule', []);

	module.directive('haCarousel', ['$timeout', '$compile', 'haUtils', 'haConfig', '$rootScope', function ($timeout, $compile, haUtils, haConfig, $rootScope) {

		return {
			restrict: 'A',
			scope: true,
			priority: 20,
			transclude: true,
			templateUrl: haConfig.getTemplateUrl('ha-carousel-template.html'),
			link: function ($scope, $el, $attr, $ctrl, $transcludeFn) {

				// options
				var onlyPage = $scope.$eval($attr.onlyPage);
				var noTransition = $scope.$eval($attr.noTransition);
				var allowWrap = ($attr.allowWrap) ? $scope.$eval($attr.allowWrap) : true;
				var mobileSlide = $scope.$eval($attr.mobileSlide);
				var isMobile = $rootScope.isMobile;

				// pagination types: "dots, paddled-dots, overlay-dots, overlay-paddled-dots"
				var paginationType = $attr.paginationType;
				var largePaddles = $scope.$eval($attr.largePaddles);
				var paginationAriaHidden = ($attr.paginationAriaHidden) ? $attr.paginationAriaHidden : false;

				// Custom transclusion to keep template scope
				$el.find('.transclude').append($transcludeFn($scope, function () {
				}));

				// If carousel has a draggable directive, restrict dragging to x-axis,
				// bind dragend to this directive's methods.
				$el.find('[ha-draggable]').each(function () {
					$(this).scope().updateDraggableSettings({
						distance: 40,
						axis: 'x'
					});

					$(this).scope().$on('$dragEnd', function (event, e, ui) {
						if (onlyPage) {
							$scope.gotoNextSlideInDirection(ui.position.left - ui.originalPosition.left);
						}
						$scope.gotoNearestSlide();
					});
				});

				$scope.slideWrapper = $el.find('.ha-carousel-slide-wrapper').first();

				// initialize with slides timeout to wait for ng-repeat linking
				$timeout(function () {
					$scope.slides = $scope.slideWrapper.children('.ha-carousel-slide');
					$scope.currentSlide = 0;
					$scope.paginationType = paginationType;
					$scope.largePaddles = largePaddles;
					$scope.paginationAriaHidden = paginationAriaHidden;
					$scope.allowWrap = allowWrap;
					$scope.slideCount = $scope.slides.length;
					$scope.slideWrapperWidth = ($scope.slideCount * 100) + '%';
					$scope.slideWidth = (100 / $scope.slideCount) + '%';
				});


				// Scope Methods
				// -------------

				// It goes to a given slide in the carousel.
				$scope.gotoSlide = function (which, speed, cb) {
					$timeout(function () {
						var targetSlide = $scope.slides[which];
						var slideRect;
						var viewportRect;

						if ($scope.isAnimating && targetSlide || !targetSlide) {
							return;
						}

						$scope.isAnimating = true;

						if (typeof speed === 'undefined') {
							speed = 350;
						}

						slideRect = targetSlide.getBoundingClientRect();
						viewportRect = $el.get(0).getBoundingClientRect();

						var afterChange = function () {
							$scope.isAnimating = false;
							if (speed > 0) {
								$el.trigger('haCarouselAtSlide', which);
							}
							if (cb) {
								cb();
							}
						};
						var animateTransition = function () {
							$scope.slideWrapper.animate({
								'left': (slideRect.right - slideRect.left) / (viewportRect.right - viewportRect.left) * -100 * which + '%'
							}, speed, afterChange);
						};
						if (noTransition) {
							if (!mobileSlide || (mobileSlide && !isMobile)) {
								$scope.slideWrapper.css('left', (slideRect.right - slideRect.left) /
									(viewportRect.right - viewportRect.left) * -100 * which + '%');
								afterChange();
							} else {
								animateTransition();
							}
						}
						else {
							animateTransition();
						}


						$scope.currentSlide = which;
					}, 0);

					return $scope;
				};

				// It moves the carousel to the next slide.
				$scope.gotoNextSlide = function () {
					var $slides = this.slideWrapper.children('.ha-carousel-slide');
					var whichSlide = !allowWrap || this.currentSlide + 1 < $slides.length ? this.currentSlide + 1 : 0;
					this.gotoSlide(whichSlide);
					return this;
				};

				// It moves the carousel to the previous slide.
				$scope.gotoPreviousSlide = function () {
					var $slides = this.slideWrapper.children('.ha-carousel-slide');
					var whichSlide = !allowWrap || this.currentSlide - 1 >= 0 ?
					this.currentSlide - 1 : $slides.length - 1;
					this.gotoSlide(whichSlide);
					return this;
				};

				$scope.gotoNextSlideInDirection = function (offsetMade, offsetNeeded) {
					var $slides = this.slideWrapper.children('.ha-carousel-slide');
					if ($slides.length === 0) {
						return;
					}
					offsetNeeded = offsetNeeded || $($slides[0]).width() / 4;
					if (offsetMade > 0 && offsetMade > offsetNeeded &&
						this.currentSlide > 0) {
						this.gotoPreviousSlide();
					} else if (offsetMade < 0 && offsetMade < -offsetNeeded &&
						this.currentSlide < $slides.length - 1) {
						this.gotoNextSlide();
					} else {
						this.gotoSlide(this.currentSlide);
					}
					this.$apply();

					return this;
				};

				// It moves the carousel to the nearest slide.
				$scope.gotoNearestSlide = function () {
					var $slides = this.slideWrapper.children('.ha-carousel-slide');
					var slidePositions = haUtils.getChildrenPositions($el, $slides);
					var closestPosition = haUtils.closestInArray(slidePositions, 0);

					this.gotoSlide(slidePositions.indexOf(closestPosition));
					this.$apply();

					return this;
				};

				// Grab the alt text from the slide content
				$scope.getAltText = function () {
					var altText = '';
					var image = $(this.slide).children('.main-image');
					if (image.length > 0) {
						altText = image[0].dataset.alt;
					}
					return altText;
				};

				// ### End: Scope Methods

				// Initialization
				// --------------
				$scope.currentSlide = 0;
				$scope.$emit('$haCarouselReady');
			}
		};
	}]);

	module.directive('haCarouselPaddles', ['haConfig', function (haConfig) {
		return {
			restrict: 'A',
			templateUrl: haConfig.getTemplateUrl('ha-carousel-paddles-template.html')
		};
	}]);

	module.directive('haCarouselPagination', ['haConfig', function (haConfig) {
		return {
			restrict: 'A',
			templateUrl: haConfig.getTemplateUrl('ha-carousel-pagination-template.html')
		};
	}]);

	module.directive('haCarouselPaginationWithPaddles', ['haConfig', function (haConfig) {
		return {
			restrict: 'A',
			templateUrl: haConfig.getTemplateUrl('ha-carousel-pagination-with-paddles-template.html')
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Passenger Count Directive
	// --------------------------------------------
	//
	// * **Class:** HaPassengerCount
	// * **Author:** Steven Tate
	//
	// ensures validity of 2 passenger type dropdowns

	'use strict';

	var mod = angular.module('haPassengerCountModule', []);

	mod.directive('haPassengerCount', [function () {

		return {
			restrict: 'A',
			require: 'ngModel',
			scope: {otherDropdownCount: '='},
			link: function ($scope, $el, $attrs, ngModel) {

				var validate = function (viewValue) {

					var otherDropdownCount = typeof $scope.otherDropdownCount === 'undefined' ? 0 : parseInt($scope.otherDropdownCount, 10);
					var paxCount = parseInt(viewValue, 10) + otherDropdownCount;
					var isValid = paxCount > 0 && paxCount < 8;

					ngModel.$setValidity('haPassengerCount', isValid);

					return viewValue;
				};

				ngModel.$parsers.unshift(validate);
				ngModel.$formatters.push(validate);

				$scope.$watch('otherDropdownCount', function () {
					return validate(ngModel.$viewValue);
				});
			}
		};

	}]);

})(angular);
;
(function (ng) {

	// Price Chart Carousel Direvtive
	// --------------------------------------------
	//
	// * **Class:** haPriceChart
	// * **Author:** Michael Toymil, Jake Albaugh
	//
	// A 25 day calendar carousel to compare ticket fares by date.

	'use strict';

	var module;
	try {
		module = ng.module('haLowFareSearchModule');
	} catch (e) {
		module = ng.module('haLowFareSearchModule', ['haPriceApiModule', 'ngAnimate', 'ngTouch']);
	}

	// Flag to keep track of animation status
	var animating = false;

	module.directive('haPriceChart', ['haPriceApiService', '$animate', 'haConfig', function (priceApi, $animate, haConfig) {
		var CEILING_FACTOR = 0.33;             // Proportion of max price to pad the top of the chart with
		var MAX_DAYS = 331;                   // How far into the future we can scroll
		var VISIBLE_DAYS_COUNT = 25;          // How many days are shown at a time.
		var PADDING_DAYS_COUNT = 15;          // How many days should we pad the visible range by on either side
		var MOVE_TIMEOUT_MILLESECONDS = 1000; // How long to wait before assuming the user is done moving.
		var moveTimeout;                      // Holds the timeout object
		var searchURL = '/Book/home?searchDetails=';

		var controller = function ($scope) {
			$scope.minPrice = Number.POSITIVE_INFINITY;
			$scope.maxPrice = Number.NEGATIVE_INFINITY;

			function resetDates() {
				$scope.selectedPrice = {
					date: new Date($scope.tripData.departureDate),
					price: 0
				};

				$scope.currentDate = new Date($scope.selectedPrice.date);
				$scope.currentDate.setDate($scope.currentDate.getDate() - parseInt(VISIBLE_DAYS_COUNT / 2));
			}

			resetDates();

			$scope.buffer = {};    // The days we've loaded so far
			$scope.visibleMonths = [];
			$scope.endOfMonthIndex = undefined;

			$scope.moving = false;

			// NOTE: resultsArray: [{Price: <number>, DepartDate: <String> }]
			function addResultsToBuffer(resultsArray) {
				for (var i = 0; i < resultsArray.length; i++) {
					var result = resultsArray[i];
					var date = new Date(result.DepartDate);
					if (date.getTime() === $scope.selectedPrice.date.getTime()) {
						$scope.selectedPrice.price = result.Price;
					}
					$scope.buffer[date.getTime()] = {
						price: result.Price,
						originalDate: result.date
					};
				}
			}

			// Repopulate array containing visible results
			function refreshVisible() {
				var newVisible = [];

				// Reset min/max
				$scope.minPrice = Number.POSITIVE_INFINITY;
				$scope.maxPrice = Number.NEGATIVE_INFINITY;

				for (var i = 0; i < VISIBLE_DAYS_COUNT; i++) {
					var tempDate = new Date($scope.currentDate);
					tempDate.setDate($scope.currentDate.getDate() + i);

					var returnDate = new Date(tempDate);
					returnDate.setDate(tempDate.getDate() + parseInt($scope.tripData.tripLength));
					// Create an object for every visible day slot
					var dayObject = {
						date: tempDate,
						returnDate: returnDate
					};
					// If we have buffer data for that day, use it
					if ($scope.buffer[tempDate.valueOf()]) {
						var price = $scope.buffer[tempDate.valueOf()].price;
						if (price) {
							// Calculate min/max price in this pass
							$scope.minPrice = Math.min($scope.minPrice, price);
							$scope.maxPrice = Math.max($scope.maxPrice, price);
						}
						dayObject.price = price;
					}

					newVisible.push(dayObject);
				}

				// Include the selected price in the calculation, for proper scaling
				$scope.minPrice = Math.min($scope.minPrice, $scope.selectedPrice.price);
				$scope.maxPrice = Math.max($scope.maxPrice, $scope.selectedPrice.price);

				// Raise the ceiling for the max price in order to keep some padding at the top of the chart.
				$scope.maxPrice += Math.round($scope.maxPrice * CEILING_FACTOR);

				$scope.visible = newVisible;
				calculatePriceRatios();
				window.setTimeout(function () {
					$animate.enabled(true);
				}, 0);
			}

			// Caluclate and set the price ratios for the results
			function calculatePriceRatios() {
				for (var i = 0; i < $scope.visible.length; i++) {
					if ($scope.visible[i].price) {
						if ($scope.visible[i].price === $scope.minPrice && $scope.visible[i].price === $scope.maxPrice) {
							$scope.visible[i].ratio = 1;
						} else {
							$scope.visible[i].ratio = $scope.visible[i].price / $scope.maxPrice;
						}
					}
				}

				$scope.selectedPrice.ratio = Math.min($scope.selectedPrice.price / $scope.maxPrice, 1);
			}

			// Using the currentDate, determine what we need to query the API for, if at all.
			function findBufferGap(direction) {
				var gap = {
					startDate: undefined,
					days: undefined
				};
				var inGap = false;
				for (var i = -PADDING_DAYS_COUNT; i < VISIBLE_DAYS_COUNT + PADDING_DAYS_COUNT; i++) {
					var tempDate = new Date($scope.currentDate);
					if (direction < 0) {
						tempDate.setDate(tempDate.getDate() + VISIBLE_DAYS_COUNT);
					}
					tempDate.setDate(tempDate.getDate() + (i * direction));

					if (!$scope.buffer[tempDate.valueOf()]) {
						if (!inGap) {
							if (gap.startDate) {
								// We've found a second gap in the buffer, call it quits and query the entire interval
								gap.startDate = new Date($scope.currentDate);
								gap.startDate.setDate($scope.currentDate.getDate() - PADDING_DAYS_COUNT);
								gap.days = VISIBLE_DAYS_COUNT + 2 * PADDING_DAYS_COUNT;
								break;
							} else {
								// This is our first gap, save the startDate
								gap.startDate = tempDate;
								gap.days = 1;
								inGap = true;
							}
						} else {
							gap.days++;
						}
					} else {
						if (inGap) {
							inGap = false;
						}
					}
				}

				return gap;
			}


			function moveCallback(direction) {
				// Determine what to query the API for
				var gap = findBufferGap(direction);
				$scope.moving = false;
				if (gap.startDate) {
					$scope.loading = true;
					priceApi.fetchPrices(formatDateForQuery(gap.startDate), direction).success(function (response) {
						if (response.IsSuccess) {
							addResultsToBuffer(response.LowFareTripResponseList);
							refreshVisible();
						}
						$scope.loading = false;
					});
				}
				$scope.$apply();
			}

			// Calculate how many days for each visible month should be shown
			function updateMonths() {
				$scope.preventLeft = false;
				$scope.preventRight = false;
				$scope.endOfMonthIndex = null;
				var visibleMonths = [];
				var tempDate = $scope.visible[0].date;
				var month = {
					month: tempDate.getMonth(),
					year: tempDate.getFullYear(),
					days: 1
				};
				for (var i = 1; i < $scope.visible.length; i++) {
					tempDate = $scope.visible[i].date;
					if (tempDate.getMonth() !== month.month) {
						visibleMonths.push(month);
						$scope.endOfMonthIndex = i - 1;
						month = {
							month: tempDate.getMonth(),
							year: tempDate.getFullYear(),
							days: 1
						};
					} else {
						month.days++;
					}

					if (tempDate <= Date.now()) {
						$scope.preventLeft = true;
					}
					if (daysBetween(new Date(), tempDate) >= MAX_DAYS) {
						$scope.preventRight = true;
					}
				}
				visibleMonths.push(month);
				$scope.visibleMonths = visibleMonths;
			}

			// Move a specified number of days
			$scope.move = function (days) {
				if (animating) {
					return;
				}
				$scope.moving = true;
				animating = true;
				$scope.currentDate.setDate($scope.currentDate.getDate() + days);
				var direction = days / Math.abs(days); // 1 or -1
				window.clearTimeout(moveTimeout);
				moveTimeout = window.setTimeout(function () {
					moveCallback(direction);
				}, MOVE_TIMEOUT_MILLESECONDS);
				refreshVisible();
				updateMonths();
			};

			var monthKeys = ['januarytext', 'februarytext', 'marchtext', 'apriltext', 'maytext', 'junetext', 'julytext', 'augusttext', 'septembertext', 'octobertext', 'novembertext', 'decembertext'];
			$scope.monthFilter = function (value) {
				return $scope.sitecoreStrings[monthKeys[value]];
			};

			$scope.barClicked = function (result) {

				var queryString = [
					'o=' + $scope.tripData.origin,
					'd=' + $scope.tripData.destination,
					'dd=' + formatDateForQuery(result.date),
					'a=' + $scope.tripData.adultCount,
					'c=' + $scope.tripData.childCount,
					'rd=' + formatDateForQuery(result.returnDate),
					'tt=' + $scope.tripData.tripType
				];

				window.location.href = searchURL + queryString.join(';');

			};

			function formatDateForQuery(date) {
				var s = '';
				s += ('0' + (date.getMonth() + 1)).slice(-2) + '/';
				s += ('0' + date.getDate()).slice(-2) + '/';
				s += date.getFullYear();
				return s;
			}

			function daysBetween(date1, date2) {
				//Get 1 day in milliseconds
				var one_day = 1000 * 60 * 60 * 24;

				// Convert both dates to milliseconds
				var date1_ms = date1.getTime();
				var date2_ms = date2.getTime();

				// Calculate the difference in milliseconds
				var difference_ms = date2_ms - date1_ms;

				// Convert back to days and return
				return Math.round(difference_ms / one_day);
			}

			$scope.$watch('results', function (nv) {
				if (nv) {
					$animate.enabled(false);
					$scope.buffer = {};
					resetDates();
					addResultsToBuffer(nv);
					refreshVisible();
					updateMonths();
				}
			});
		};

		controller.$inject = ['$scope'];

		return {
			restrict: 'A',
			scope: {
				tripData: '=',
				sitecoreStrings: '=',
				sitecoreStringsLoaded: '=',
				results: '=',
				loading: '=',
				error: '='
			},
			controller: controller,
			templateUrl: haConfig.getTemplateUrl('ha-price-chart-base-template.html')
		};
	}]);


	// Animations
	module.animation('.month-animation', function () {
		return {
			enter: function (element, done) {
				element.css({'opacity': 0});
				element.delay(500).animate({'opacity': 1}, {
					complete: done
				});
			}
		};
	});
	module.animation('.price-item-animation', function () {
		return {
			enter: function (element, done) {
				if (element.hasClass('first-item')) {
					// The very first element
					var oldMargin = element.css('margin-left');
					element.css({
						'margin-left': 0
					});
					element.animate({
						'margin-left': oldMargin
					}, {
						easing: 'linear',
						duration: 500,
						complete: done
					});
					return function () {
						element.css({
							'margin-left': ''
						});
						animating = false;
					};
				} else {
					element.animate({
						'background-color': 'transparent'
					}, {
						duration: 500,
						complete: done
					});
				}
			},
			leave: function (element, done) {
				element.animate({
					'margin-left': 0
				}, {
					easing: 'linear',
					duration: 500,
					complete: done
				});

				if (element.hasClass('first-item')) {
					var newFirst = element.nextAll('.first-item').first();
					newFirst.css({
						'margin-left': 0
					});
					return function () {
						newFirst.css({
							'margin-left': ''
						});
						animating = false;
					};
				}
			}
		};

	});

})(angular);
;
(function (angular) {

	'use strict';

	var module = angular.module('haDatepickerModule', []);

	module.directive('haDatepicker2', ['$rootScope', 'haTemplateCache', 'haConfig', '$filter', '$locale', '$compile', function ($rootScope, haTemplateCache, haConfig, $filter, $locale, $compile) {

		return {
			require: 'ngModel',
			restrict: 'A',
			scope: true,

			link: function (scope, $el, attrs, ctrl) {
				var config = $el.attr('autocomplete', 'off').attr('ha-datepicker2');
				var options = scope.$eval(config) || {};
				options.format = options.format || 'MM/DD/YYYY'; //$locale.DATETIME_FORMATS.shortDate.toUpperCase();//this is used by moment, so we need to uppercase it
				options.display = options.display || ('EEEE, ' + $locale.DATETIME_FORMATS.shortDate);

				var $parent_form = $($el[0].form);
				var cal_scope = scope.$new();
				var template = '<div ha-calendar="' + config + '" ha-calendar-model="' + attrs.ngModel + '"></div>';
				var $cal = $compile(template)(cal_scope);
				var $datepicker = $('<div class="datepicker">').click(focus).insertAfter($el).append($cal);

				// ngModel requires a name- but if we are posting the form, we don't want one. (Because the date's format is not ISO8610)
				// so, as a work around, we can use a name with an underscore and then remove it.
				if ($el[0].name === '_') {
					$el.attr('name', null);
				}

				if ($rootScope.isMobile) {
					$el.attr('readonly','readonly');
				}

				var closer, down;
				$el.on('focus', function () {
					$datepicker.css('top', $el.position().top + $el.outerHeight() + 'px');
					$datepicker.toggle(true);
					$el.addClass('open');
					clearTimeout(closer);
					$el.val(ctrl.$viewValue || '').select();
				});

				$el.on('blur', function () {
					if (down) {
						return;
					}
					closer = setTimeout(function () {
						$datepicker.toggle(false);
						$el.removeClass('open');
						//do not format_pretty if invalid
						if (ctrl.$valid) {
							$el.val(ctrl.$modelValue ? format_pretty(ctrl.$modelValue) : '');
						}
					}, 200);
				});

				function mouseup() {
					down = false;
					$(document.body).off('mouseup blur', mouseup);
				}

				$datepicker.on('mousedown', function () {
					down = true;
					$(document.body).on('mouseup blur', mouseup);
				});

				function focus() {
					// On the mobile version we can't have the screen scrolling around with focus
					if ($rootScope.isMobile) {
						$el.select();
						return;
					}
					if (document.activeElement !== $el[0]) {
						$el.focus().select();
					}
				}

				scope.$on('prev', focus);
				scope.$on('next', focus);

				// Calendar clicked
				scope.$on('select', function () {
					if (options.next === false) {
						return;
					}
					setTimeout(function () {
						if (options.next) {
							return $(options.next).focus();
						}

						//auto advance to the next input
						var $inputs = $parent_form.find(':input:visible:enabled:not(button.prev,button.next)');
						var $nextInput = $inputs.eq($inputs.index($el) + 1);

						// on mobile, we want blur manually
						if ($rootScope.isMobile) {
							$el.blur();
						}
						$nextInput.focus();
					}, 10);
				});

				function inRange(date) {
					return date.valueOf() >= options.range_start && date.valueOf() <= options.range_end;
				}

				ctrl.$parsers.push(function (value) {
					// check if value is in the MM/DD/YYYY format, if not, try to convert it
					if (value && typeof value === 'string' && !value.match(/^\d{2}\/\d{2}\/\d{4}$/)) {
						var pieces = value.match(/\d+/g);
						for (var i = 0; i < 2 && i < pieces.length; i++) {
							if (pieces[i].length === 1) {
								pieces[i] = '0' + pieces[i];
							}
						}
						value = pieces.join('/');
					}

					var date = value instanceof Date ? moment(value) : moment(value, options.format, true);
					var valid = !value || (date.isValid() && inRange(date));
					ctrl.$setValidity('date', valid);

					return valid && value ? date.toDate() : undefined;
				});

				ctrl.$formatters.push(function (value) {
					var valid = !value || (!invalidDate(value) && inRange(value));
					ctrl.$setValidity('date', valid);

					if (valid && value && !$el.is(':focus')) {
						setTimeout(function () {
							$el.val(format_pretty(value));
						}, 50);
					}
					return valid && value ? moment(value).format(options.format) : '';
				});

				function format_pretty(value) {
					return invalidDate(value) ? '' : $filter('date')(value, options.display);
				}

				function invalidDate(date) {
					return !date || (!moment.isMoment(date) && !(date instanceof Date)) || isNaN(date);
				}
			}
		};
	}]);
	module.directive('haRangeDatepicker', ['$parse', 'haSitecoreStrings', function ($parse, $scs) {
		return {
			restrict: 'A',
			link: {
				pre: function (scope, $el, attrs) {
					var departdateval = $scs('BookingWidget.departdate');
					var returndateval = $scs('BookingWidget.returndate');
					if ($el.context.textContent && $el.context.textContent.length > 0) {
						departdateval = $el.context.textContent.split('\n')[4].replace(',', '');
						returndateval = $el.context.textContent.split('\n')[13].replace(',', '');
					}
					
					var options = $.extend(true, {
						calendars: 2,
						viewing: moment().startOf('month'),
						namespace: '',
						format: 'MM/DD/YYYY', //$scs('Forms.DateFormat').replace(/\(|\)/g,'').toUpperCase(),
						range_start: moment().startOf('day'),
						range_end: moment().add(331, 'days'),
						depart_cal_title: departdateval,
						return_cal_title: returndateval,
						rangeRender: function ($cal) {
							$cal.find('td[date]').removeClass('start end between').each(function () {
								var $td = $(this);
								var date = moment($td.attr('date'), 'YYYY-MM-DD');
								var type = getClass(date);
								if (type) {
									$td.addClass(type);
								}
							});
						}
					}, scope.$eval(attrs.haRangeDatepicker));

					function clone_config(isReturn) {
						return $.extend({}, options, {
							range_start: moment(options.range_start),
							range_end: moment(options.range_end),
							calendarHeading: (isReturn) ? options.return_cal_title : options.depart_cal_title,
							isReturn: isReturn
						});
					}

					scope[options.namespace + '_configs'] = {
						start: clone_config(false),
						end: clone_config(true)
					};

					function prop(expression) {
						var getter = $parse(expression);
						return {
							get: getter.bind(getter, scope),
							set: getter.assign.bind(getter, scope)
						};
					}

					var start_model = $el.find(options.start).attr('ha-datepicker2', options.namespace + '_configs.start').attr('ng-model');
					var end_model = $el.find(options.end).attr('ha-datepicker2', options.namespace + '_configs.end').attr('ng-model');

					var model = {
						start: prop(start_model),
						end: prop(end_model)
					};

					scope.$watch(start_model, function (start_value) {
						if (start_value) {
							start_value = moment(start_value);
							var end = model.end.get();
							if (end && start_value.isAfter(end)) {
								model.end.set(undefined);
							}

							/* as date changes, preserve calendar view based on chosen month/day, but don't
							shift calendar position if a date on the right side of the panel is selected */
							var startMonth = start_value.month();
							if (start_value.year() > options.viewing.year()) {
								startMonth += 12;
							}

							if (startMonth > options.viewing.month()) {
								options.viewing.year(start_value.year()).month(start_value.month() - 1);
							} else {
								options.viewing.year(start_value.year()).month(start_value.month());
							}
						}
						options.viewing.add(1, 'ms');//hack to make it re-render
					});
					scope.$watch(end_model, function (end_value) {
						if (end_value) {
							end_value = moment(end_value);
							var start = model.start.get();
							if (start && end_value.isBefore(start)) {
								model.start.set(undefined);
							}
						}
						options.viewing.add(1, 'ms');//hack to make it re-render
					});

					function getClass(date) {
						var end = model.end.get();
						var start = model.start.get();
						if (end && start && scope.tripType === 2 && (date.isBefore(end) && date.isAfter(start))) {
							return 'between';
						}
						var isDepart = start && date.isSame(start);
						var isReturn = end && date.isSame(end);
						if (isDepart && isReturn) {
							return;
						}//no classes. The `selected` class will provide styling
						if (isDepart) {
							return 'start';
						}
						if (isReturn && scope.tripType === 2) {
							return 'end';
						}
					}
				}
			}
		};
	}]);


	//THIS IS DEPRECATED!
	module.directive('haDatepicker', ['haTemplateCache', 'haConfig', function (haTemplateCache, haConfig) {

		haTemplateCache.get(haConfig.getTemplateUrl('ha-calendar2.html'));
		haTemplateCache.get(haConfig.getTemplateUrl('ha-booking-day-template.html'));
		haTemplateCache.get(haConfig.getTemplateUrl('ha-datepicker-day-template.html'));

		return {
			templateUrl: haConfig.getTemplateUrl('ha-datepicker.html'),
			restrict: 'A',
			scope: {
				label: '@',
				eyebrow: '@',
				placeholder: '@',
				name: '@',
				inputModel: '=',
				monthsBackward: '=',
				monthsForward: '=',
				monthsToShow: '=',
				dayTemplate: '@',
				errorMessage: '@',
				pastDisabled: '@',
				futureDisabled: '@'
			},
			link: function (scope, elem, attrs) {
				scope.calendarOpen = false;
				scope.monthsToShow = scope.monthsToShow || 1;
				scope.pastDisabled = attrs.hasOwnProperty('pastDisabled');
				scope.futureDisabled = attrs.hasOwnProperty('futureDisabled');
				scope.dayTemplate = scope.dayTemplate || 'ha-datepicker-day-template.html';
				scope.required = false;

				attrs.$observe('required', function () {
					if (attrs.hasOwnProperty('required')) {
						scope.required = true;
					}
				});
				//scope.ngRequired = scope.ngRequired || attrs.hasOwnProperty('required');

				// Date Input focused
				scope.$on('dateInputFocused', function () {
					scope.calendarOpen = true;
					wireCalendarCloseEvent();
				});

				// Calendar clicked
				scope.$on('setDate', function (e, date) {
					if (scope.calendarOpen) {
						scope.inputModel = date || '';
						scope.$apply();
					}
				});

				scope.$watch('inputModel', function () {
					scope.calendarOpen = false;
				});

				attrs.$observe('eyebrow', function (newVal, oldVal) {
					if (newVal !== oldVal) {
						scope.eyebrow = newVal;
					}
				});

				attrs.$observe('label', function (newVal, oldVal) {
					if (newVal !== oldVal) {
						scope.label = newVal;
					}
				});

				attrs.$observe('placeholder', function (newVal, oldVal) {
					if (newVal !== oldVal) {
						scope.placeholder = newVal;
					}
				});

				function wireCalendarCloseEvent() {
					$('body').off('click.closeCalendar');
					$('body').on('click.closeCalendar', function (e) {
						var $targ = $(e.target);

						if (!scope.calendarOpen || (scope.calendarOpen && $targ.closest('[ha-datepicker]').length)) {
							return;
						}

						if ($targ.closest('.calendarPopupWrap').length > 0 || $targ.is('.calendarPopupWrap')) {
							return;
						}

						$('body').off('click.closeCalendar');
						scope.currentDateChoice = '';
						scope.calendarOpen = false;
						scope.$digest();
					});
				}
			}
		};
	}]);

	//THIS IS DEPRECATED!
	// DATE INPUT FIELDS
	module.directive('haDatepickerInput', ['haTemplateCache', 'haConfig', function (haTemplateCache, haConfig) {

		haTemplateCache.get(haConfig.getTemplateUrl('ha-calendar2.html'));
		haTemplateCache.get(haConfig.getTemplateUrl('ha-booking-day-template.html'));

		return {
			templateUrl: haConfig.getTemplateUrl('ha-datepicker-input.html'),
			scope: {
				inputModel: '=',
				calendarOpen: '=',
				currentDateChoice: '=',
				inputName: '@name',
				label: '@',
				eyebrow: '@',
				placeholder: '@',
				disabled: '=',
				ngRequired: '=',
				errorMessage: '='
			},
			link: function (scope, element, attrs) {

				scope.onFocus = function () {
					scope.$emit('dateInputFocused', element);
				};

				scope.isActive = function () {
					return scope.calendarOpen;
				};

				attrs.$observe('eyebrow', function (newVal, oldVal) {
					if (newVal !== oldVal) {
						scope.eyebrow = newVal;
					}
				});

				attrs.$observe('label', function (newVal, oldVal) {
					if (newVal !== oldVal) {
						scope.label = newVal;
					}
				});

				attrs.$observe('placeholder', function (newVal, oldVal) {
					if (newVal !== oldVal) {
						scope.placeholder = newVal;
					}
				});
			}
		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	// Hawaiian Neat Forms
	// ===============================
	//
	// * **Class:** HaNeatForms
	// * **Author:** Jamie Perkins
	//
	// Forms to hand user off to Neat for Hotels & Packages

	var module = angular.module('haNeatFormsModule', []);

	module.directive('haNeatForms', ['haUtils', 'haDateUtils', '$window', 'haCitiesSvc', '$filter', 'localShortDateFilter', '$timeout', 'haConfig', 'haSitecoreStrings', '$log',
		function (haUtils, haDateUtils, $window, haCitiesSvc, $filter, localShortDateFilter, $timeout, haConfig, $scs, $log) {

			return {
				templateUrl: haConfig.getTemplateUrl('ha-neat-forms-template.html'),
				restrict: 'A',
				scope: {
					formType: '@',
                    onMauve: '@'
				},
				link: function ($scope, $el, $attrs) {
					// form stuff
					$scope.language = $language;
					$scope.Error = '';
					$scope.passengerOptions = [1, 2, 3, 4, 5, 6];
					$scope.childPassengerOptions = [0, 1, 2, 3, 4, 5, 6];
					$scope.childPassengerAge = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17];
					$scope.roomOptions = [];
					$scope.packageTimes = [
						{
							name: $scs('BookingWidget.anytime'),
							val: 'Anytime'
						}, {
							name: $scs('BookingWidget.morning'),
							val: 'Morning'
						}, {
							name: $scs('BookingWidget.noon'),
							val: 'Noon'
						}, {
							name: $scs('BookingWidget.evening'),
							val: 'Evening'
						}, {
							name: '12:00 am',
							val: 'TwelveAm'
						}, {
							name: '1:00 am',
							val: 'OneAm'
						}, {
							name: '2:00 am',
							val: 'TwoAm'
						}, {
							name: '3:00 am',
							val: 'ThreeAm'
						}, {
							name: '4:00 am',
							val: 'FourAm'
						}, {
							name: '5:00 am',
							val: 'FiveAm'
						}, {
							name: '6:00 am',
							val: 'SixAm'
						}, {
							name: '7:00 am',
							val: 'SevenAm'
						}, {
							name: '8:00 am',
							val: 'EightAm'
						}, {
							name: '9:00 am',
							val: 'NineAm'
						}, {
							name: '10:00 am',
							val: 'TenAm'
						}, {
							name: '11:00 am',
							val: 'ElevenAm'
						}, {
							name: '12 Noon',
							val: 'Noon'
						}, {
							name: '1:00 pm',
							val: 'OnePm'
						}, {
							name: '2:00 pm',
							val: 'TwoPm'
						}, {
							name: '3:00 pm',
							val: 'ThreePm'
						}, {
							name: '4:00 pm',
							val: 'FourPm'
						}, {
							name: '5:00 pm',
							val: 'FivePm'
						}, {
							name: '6:00 pm',
							val: 'SixPm'
						}, {
							name: '7:00 pm',
							val: 'SevenPm'
						}, {
							name: '8:00 pm',
							val: 'EightPm'
						}, {
							name: '9:00 pm',
							val: 'NinePm'
						}, {
							name: '10:00 pm',
							val: 'TenPm'
						}, {
							name: '11:00 pm',
							val: 'ElevenPm'
						}
                    ];
                    $scope.getMediaImage = haUtils.getImageFromSiteCoreString;
					//Fn to update Scope.packageTimes for Expedia booking on US site.
					if ($scope.language === 'en') {
						$scope.packageTimes = $scope.packageTimes.map(function (x) {
							if (x.val === 'Anytime') {
								x.val = 362;
							} else if (x.val === 'Morning') {
								x.val = 361;
							} else if (x.val === 'Noon') {
								x.val = 721;
							} else if (x.val === 'Evening') {
								x.val = 1081;
							} else {
								var first = 2;
								if (x.name.indexOf(':') === 1) {
									first = 1;
								}
								x.val = x.name.substr(0, first) + x.name.substr(-2).toUpperCase();
							}
							return x;
						});
					}

					// for compatability with ha-datepicker2
					$scope.tripType = 2;
					$scope.legs = [];

					$scope.strings = {};
					$scs.get('BookingWidget').then(function (data) {
						$scope.strings = data;
						//$log.debug('strings', $scope.strings);						
						if ($attrs.formType === 'hotels') {
							$scope.maxRooms = 8;
							$scope.model.neatformbuttontext = $scope.strings.bookhotelbuttontext;
							$scope.model.dynamicDestinationText = $scope.strings.destinationtext;
							$scope.model.dynamicDepartText = $scope.strings.checkintext;
							$scope.model.dynamicReturnText = $scope.strings.checkouttext;
							$scope.model.thirdPartyPartnerImage = ($scope.onMauve === 'true')
								? $scope.strings.bghotelsthirdpartypartnerimage
								: $scope.strings.nobghotelsthirdpartypartnerimage;
							for (var i = 0; i < $scope.maxRooms; i++) {
								$scope.roomOptions[i] = i + 1;
							}
							$scope.model.enableHelpLink = true;
							if (!$scope.strings.vacationrentalshelpurl) {
								$scope.model.enableHelpLink = false;
							}
						} else if ($attrs.formType === 'packages') {
							$scope.maxRooms = 3;
							$scope.model.neatformbuttontext = $scope.strings.continuebuttontext;
							$scope.model.dynamicDestinationText = $scope.strings.to;
							$scope.model.dynamicDepartText = $scope.strings.departtext;
							$scope.model.dynamicReturnText = $scope.strings.returntext;
							$scope.model.thirdPartyPartnerImage = ($scope.onMauve === 'true')
								? $scope.strings.bgpackagesthirdpartypartnerimage
								: $scope.strings.nobgpackagesthirdpartypartnerimage;
							for (var j = 0; j < $scope.maxRooms; j++) {
								$scope.roomOptions[j] = j + 1;
							}
						}
					});

					// new datepicker fns

					// Get calendar heading
					function getCalendarHeading(legType) {
						if (!$scope.strings.departdate) {
							return '';
						}
						var departText = $scope.strings.departdate;
						var returnText = $scope.strings.returndate;
						if ($scope.model.packageType.indexOf('A') < 0 && $attrs.formType === 'hotels') {
							departText = $scope.strings.checkintext;
							returnText = $scope.strings.checkouttext;
						}
						return (legType !== 'returnDate') ? departText : returnText;
					}

					// defaults
					$scope.model = {
						adultCount: 1,
						childCount: 0,
						roomCount: 1,
						rooms: [
						{
							adultCount: 1,
							childrenCount: 0,
							child: []
						}],
						departDate: '',
						returnDate: '',
						originCity: '',
						destinationCity: '',
						packageType: '',
						airClass: 'EconomyClass',
						fromTime: $scope.packageTimes[0],
						toTime: $scope.packageTimes[0],
						formType: $scope.formType,
						onMauve: $scope.onMauve,
						neatforms_datepicker_config: {
							start: '[name=_FromDate]',
							end: '[name=_ToDate]',
							depart_cal_title: getCalendarHeading('departDate'),
							return_cal_title: getCalendarHeading('returnDate'),
							namespace: 'neatforms',
							onRender: function ($cal) {
								$cal.addClass('double-wide');
							}
						}
					};

					// watch so that unavailable days can be updated
					$scope.$on('airportChanged', function () {

						if ($scope.model.packageType.indexOf('A') >= 0 &&
							$scope.model.originCity &&
							$scope.model.originCity.Code &&
							$scope.model.destinationCity &&
							$scope.model.destinationCity.Code) {

							$scope.legs[0] = {
								origin: { Code: $scope.model.originCity.Code },
								destination: { Code: $scope.model.destinationCity.Code }
							};
							$scope.$broadcast('airport changed');
						}
					});

					$scope.$watch('model.rooms', function () {
						if ($scope.Error !== '') {
							$scope.Error = '';
						}
					}, true);


					// form type
					if ($attrs.formType === 'hotels') {
						$scope.model.packageType = 'H';
					} else if ($attrs.formType === 'packages') {
						$scope.model.packageType = 'AH';

						$scope.$watch(function (scope) {
							if (scope.model) {
								return scope.model.packageType;
							}
						}, function (newValue) {
							if (newValue && newValue.indexOf('A') < 0) { // if it does not contain a flight
								$scope.model.dynamicDestinationText = $scope.strings.destinationtext;
								$scope.model.dynamicDepartText = $scope.strings.checkintext;
								$scope.model.dynamicReturnText = $scope.strings.checkouttext;
							} else {
								$scope.model.dynamicDestinationText = $scope.strings.to;
								$scope.model.dynamicDepartText = $scope.strings.departtext;
								$scope.model.dynamicReturnText = $scope.strings.returntext;
							}
						});

					};

					// pre-select flightQuery cookie values
					var flightQueryCookie = haUtils.getFlightQueryModelCookie();
					if (flightQueryCookie) {
						// $log.debug('flight query model cookie');
						// $log.debug(flightQueryCookie);

						$scope.model.adultCount = flightQueryCookie.AdultCount || 1;
						$scope.model.childCount = flightQueryCookie.ChildCount || 0;

						if (flightQueryCookie.FlightSearchSegmentList[0]) {

							var originCode = flightQueryCookie.FlightSearchSegmentList[0].OriginCityCode;
							var destinationCode = flightQueryCookie.FlightSearchSegmentList[0].DestinationCityCode;
							// get cities from airport code
							haCitiesSvc.getCityByCode(originCode).then(function (airport) {
								// $log.debug(airport);
								$scope.model.originCity = airport;
							});
							haCitiesSvc.getCityByCode(destinationCode).then(function (airport) {
								$scope.model.destinationCity = airport;
							});

							// dates
							$scope.model.departDate = processDate(flightQueryCookie.FlightSearchSegmentList[0].DepartureDate);
							// $log.debug('set depart date: '+$scope.model.departDate);
						}
						if (flightQueryCookie.FlightSearchSegmentList[1]) {
							$scope.model.returnDate = processDate(flightQueryCookie.FlightSearchSegmentList[1].DepartureDate);
							// $log.debug('set return date: '+$scope.model.returnDate);
						}

					}

					$scope.updateChildren = function (i, n) {
						var num;
						if (n > $scope.model.rooms[i].child.length) {
							num = n - $scope.model.rooms[i].child.length;
							for (var r = 0; r < num; r++) {
								$scope.model.rooms[i].child.push({});
							}
						} else {
							num = $scope.model.rooms[i].child.length - n;
							if (num !== 0) {
								for (var c = 0; c < num; c++) {
									$scope.model.rooms[i].child.pop();
								}

							}
						}
					};
					$scope.updateRooms = function (n) {
						var num;
						if (n > $scope.model.rooms.length) {
							num = n - $scope.model.rooms.length;
							for (var r = 0; r < num; r++) {
								$scope.model.rooms.push({ adultCount: 1, childrenCount: 0, child: [] });
							}
						} else {
							num = $scope.model.rooms.length - n;
							if (num !== 0) {
								for (var i = 0; i < num; i++) {
									$scope.model.rooms.pop();
								}

							}
						}
					};

					$scope.clearRooms = function () {
						$scope.model.roomCount = 1;
						$scope.model.rooms = [
							{
								adultCount: $scope.model.rooms[0].adultCount,
								childrenCount: $scope.model.rooms[0].childrenCount,
								child: $scope.model.rooms[0].child
							}
						];
					}
					$scope.formatString = function (string, arr) {
						return haUtils.formatDynamicString(string, arr);
					};

					$scope.setForm = function (form) {
						$scope.form = form;
					};

					$scope.bizClassAvailable = function () {
						if (typeof $scope.model.destinationCity === 'object' && +$scope.model.destinationCity.Market === 3) {
							return true;
						} else if (typeof $scope.model.originCity === 'object' && +$scope.model.originCity.Market === 3) {
							return true;
						} else {
							return false;
						}
					};

					$scope.submitForm = function (e) {
						$scope.Error = '';
						if ($scope.language === 'cn'||$scope.language === 'kr' ) {
							$scope.submitNeatForm(e);
						} else {
							$scope.submitExpediaForm(e);
						}
					}

					$scope.enableHotelAndCarTab = function () {
						if ($scs('BookingWidget.hotelandcartext') == null || $scs('BookingWidget.hotelandcartext') === "" || $scs('BookingWidget.hotelandcartext') === "[BookingWidget.hotelandcartext]") {
							return false;
						} else {
							return true;
						}
					}
					$scope.enableHotelTab = function () {
						if ($scs('BookingWidget.hotelstext') == null || $scs('BookingWidget.hotelstext') === "" || $scs('BookingWidget.hotelstext') === "[BookingWidget.hotelstext]") {
							return false;
						} else {
							return true;
						}
					}

					$scope.enableVacationTab = false;
					if ($scs('BookingWidget.enablevacationrentals') === "1") {
						$scope.enableVacationTab = true;
					}

					$scope.submitNeatForm = function (e) {
						e.preventDefault();
						$scope.neatFormSubmitAttempt = true;

						// from/to
						var destinationCode;
						var destinationCityString = $scope.model.destinationCity;
						if (typeof $scope.model.destinationCity === 'object') {
							destinationCityString = $scope.model.destinationCity.LongDescription;
							destinationCode = $scope.model.destinationCity.Code;
						}
						var originCode;
						if (typeof $scope.model.originCity === 'object') {
							originCode = $scope.model.originCity.Code;
						}

						// format dates as shortdates
						var fromDate = $filter('localShortDate')($scope.model.departDate, 'en'); // neat always wants US formatted date
						var toDate = $filter('localShortDate')($scope.model.returnDate, 'en');

						var postObject = {
							DD: ($scope.model.packageType !== 'H') ? $scs('BookingWidget.neatgroupddparam') || 'HAWAIIANAIR' : $scs('BookingWidget.hotelneatgroupddparam') || 'HAWAIIANAIR',
							combinationType: $scope.model.packageType,
							adultsNum: $scope.model.adultCount,
							doSearch: 'T'
						};
						// create param for each child (ugh)
						var kids = Number($scope.model.childCount);
						if (kids > 0) {
							for (var i = 0; i < kids; i++) {
								var key = 'minorAge' + (i + 1);
								postObject[key] = 6;
							}
						}
						// dates
						if ($attrs.formType === 'hotels') {
							postObject.toLocation = $scope.isJP() ? $scope.model.destinationCity.SearchTags.split(',').slice(0, 2).join(',') : destinationCityString;
							postObject.hotelFromDate = fromDate;
							postObject.hotelToDate = toDate;
							postObject['WT.mc_id'] = haUtils.webtrends.token('HotelWidget');
						} else {
							postObject.toLocation = destinationCode;
							postObject.fromLocation = originCode;
							postObject.fromDate = fromDate;
							postObject.toDate = toDate;
							postObject.fromTime = $scope.model.fromTime.val;
							postObject.toTime = $scope.model.toTime.val;
							postObject['WT.mc_id'] = haUtils.webtrends.token('PackageWidget');
						}
						// hotel
						if ($scope.model.packageType.indexOf('H') >= 0) {
							postObject.roomsNum = $scope.model.roomCount;
						}
						// flight
						if ($scope.model.packageType.indexOf('A') >= 0) {
							postObject.airClass = $scope.model.airClass;
							postObject.fromLocation = originCode;
						}
						// fix for 154151
						if ($attrs.formType === 'packages' && ($language === 'en-au' || $language === 'en-nz')) {
							postObject.referrerId = 'Intl';
						}

						if ($scope.form.$valid) {
							var loc;
							if ($attrs.formType === 'packages') {
								postObject.DD = $scs('BookingWidget.neatgroupddparam') || 'HAWAIIANAIR';
								loc = $scs('BookingWidget.searchpackagesurl') + haUtils.createQueryString(postObject);
							} else {
								loc = $scs('BookingWidget.bookhotelurl') + haUtils.createQueryString(postObject);
							}

							$log.debug(loc);
							location.href = loc;
						} else {
							// $log.debug('error');
							$scope.Error = $scs('BookingWidget.pleasecorrecttheerrorsbelow');
						}
					};


					$scope.submitExpediaForm = function (e) {
						if ($attrs.formType === 'hotels' && $scope.model.packageType === 'HC') {
							$attrs.formType = 'packages';
						}
						e.preventDefault();
						$scope.neatFormSubmitAttempt = true;

						var validPassengerCount = verifyPassengerCount();
						var validChildrenInLap = verifyChildInLap();
						var postObject = {};

						// format dates as YYYY-MM-DD
						var fromDate = !!$scope.model.departDate ? $scope.model.departDate.YYYY_MM_DD() : undefined; // neat always wants US formatted date
						var toDate = !!$scope.model.returnDate ? $scope.model.returnDate.YYYY_MM_DD() : undefined;

						// from/to
						var destinationCode = !!$scope.model.destinationCity ? $scope.model.destinationCity.Code : undefined;
						var originCode = !!$scope.model.originCity ? $scope.model.originCity.Code : undefined;

						// dates

						if ($attrs.formType === 'hotels') {
							postObject.cityName = destinationCode || !!$scope.model.destinationCity && !!$scope.model.destinationCity.DisplayName ? $scope.model.destinationCity.DisplayName : '';
						} else {
							postObject.FromAirport = originCode;
							postObject.Destination = destinationCode;
							postObject.ToTime = $scope.model.toTime.val;
							postObject.FromTime = $scope.model.fromTime.val;
						}
						// hotel
						if ($scope.model.packageType.indexOf('H') >= 0) {
							postObject.NumRoom = $scope.model.roomCount;
						}
						// flight
						if ($scope.model.packageType.indexOf('A') >= 0) {
							// Taking first letter of class for expedia update.
							postObject.cabinClass = $scope.model.airClass.substr(0, 1).toLowerCase();
						}
						// dynamically create postObject

						var childCount = 0;
						var adultCount = 0;
						var infantInSeat = false;
						for (var r = 0; r < $scope.model.rooms.length; r++) {
							childCount += $scope.model.rooms[r].child.length;
							adultCount += $scope.model.rooms[r].adultCount;
							var adultKey;
							var childCountKey;
							if ($attrs.formType === 'hotels') {
								adultKey = 'NumAdult' + (r + 1);
							} else if ($scope.model.packageType === 'AC') {
								adultKey = 'NumAdult';
								childCountKey = 'NumChild';
							} else {
								adultKey = 'NumAdult-Room' + (r + 1);
								childCountKey = 'NumChild-room' + (r + 1);
							}
							postObject[adultKey] = $scope.model.rooms[r].adultCount;

							if ($attrs.formType === 'packages') {
								postObject[childCountKey] = $scope.model.rooms[r].child.length;
							}
							for (var c = 0; c < $scope.model.rooms[r].child.length; c++) {
								var childKey;
								if ($attrs.formType === 'hotels') {
									childKey = 'Rm' + (r + 1) + 'Child' + (c + 1) + 'Age';
								} else if ($scope.model.packageType === 'AC') {
									childKey = 'Child' + (c + 1) + 'Age';
								} else {
									childKey = 'Room' + (r + 1) + '-Child' + (c + 1) + 'Age';
								}

								if ($scope.model.rooms[r].child[c].seat === 'Seat') {
									infantInSeat = true;
								}
								if (infantInSeat && $scope.model.rooms[r].child[c].age < 2 && $scope.model.rooms[r].child[c].seat === 'Seat'
								) {
									postObject[childKey] = 2;
								} else {
									postObject[childKey] = $scope.model.rooms[r].child[c].age;

								}

							}
						}
						if ($attrs.formType === 'packages') {
							postObject.NumAdult = adultCount;
							postObject.NumChild = childCount;
						}



						var packageType;
						switch ($scope.model.packageType) {
							case 'AH':
								packageType = 'FlightHotel';
								break;
							case 'AHC':
								packageType = 'FlightHotelCar';
								break;
							case 'AC':
								packageType = 'FlightCar';
								break;
							case 'HC':
								packageType = 'HotelCar';
								break;
							case 'VR':
								packageType = 'VacationRental';
								break;
							default:
								packageType = 'FlightHotel';
						}

						if ($attrs.formType === 'hotels') {
							postObject['mdpcid'] = haUtils.webtrends.tokenV2("HTLWIDGET.HOTEL");
						} else if ($attrs.formType === 'packages') {

							// TODO: once Target 0030 is complete, restore this block to only:
							// postObject['mdpcid'] = haUtils.webtrends.tokenV2("PKGWIDGET.PACKAGE");
							if (typeof campaignid_0030 !== 'undefined') {
								postObject['mdpcid'] = campaignid_0030;
							} else {
								postObject['mdpcid'] = haUtils.webtrends.tokenV2("PKGWIDGET.PACKAGE");
							}

						} else {
							postObject['mdpcid'] = haUtils.webtrends.tokenV2("FLTWIDGET.PACKAGE");
						}

						if ($scope.form.$valid && validPassengerCount && validChildrenInLap) {
							var loc, url;
							var queryString = haUtils.createQueryString(postObject);
							if ($attrs.formType === 'packages') {
								url = $scs('BookingWidget.searchpackagesurl'); //2017-03-01
								loc = url + packageType + '/' + fromDate + '/' + toDate + queryString;
							} else {
								url = $scs('BookingWidget.bookhotelurl'); //2017-03-01
								loc = url + fromDate + '/' + toDate + queryString;
							}
							$log.debug(loc);
							location.href = loc;
						} else {
							// $log.debug('error');
							$scope.Error = $scs('BookingWidget.pleasecorrecttheerrorsbelow');
							if (!validPassengerCount) {
								$scope.Error = $scs('BookingWidget.InvalidPackagePassengerCount');
							}
							if (!validChildrenInLap) {
								$scope.Error = $scs('BookingWidget.LapChildErrorText');
							}
						}
					};

					function verifyPassengerCount() {
						var totalPassengers = 0;
						var valid = false;
						if ($attrs.formType === 'hotels') {
							valid = true;
						} else {
							for (var i = 0; i < $scope.model.rooms.length; i++) {
								totalPassengers += $scope.model.rooms[i].adultCount;
								totalPassengers += $scope.model.rooms[i].child.length;
							}
							if (totalPassengers >= 1 && totalPassengers <= 6) {
								valid = true;
							}
						}
						return valid;
					};

					function verifyChildInLap() {
						var lapChildren = 0;
						var adults = 0;
						var valid = false;
						for (var i = 0; i < $scope.model.rooms.length; i++) {
							adults += $scope.model.rooms[i].adultCount;
							for (var j = 0; j < $scope.model.rooms[i].child.length; j++) {
								if ($scope.model.rooms[i].child[j].age >= 12) {
									adults += 1;
								} else if ($scope.model.rooms[i].child[j].seat === 'Lap') {
									lapChildren += 1;
								}
							}
						}
						if (lapChildren <= adults) {
							valid = true;
						}
						return valid;
					}

					function processDate(date) {
						var dateString = date.substr(0, 10);
						var parts = dateString.split('-');
						return new Date(parts[0], parts[1] - 1, parts[2]);
					}

				}
			};

		}]);

})(angular);
;
(function (angular) {

	'use strict';

	// Hawaiian Cartrawler Forms
	// ===============================
	//
	// * **Class:** HaCartrawlerForms
	// * **Author:** Jamie Perkins
	//
	// Forms to hand user off to Cartrawler for cars or shuttles

	var module = angular.module('haCartrawlerFormsModule', []);

	module.directive('haCartrawlerForms', ['haUtils', 'haDateUtils', '$window', 'haCitiesSvc', '$timeout', 'haConfig', 'haSitecoreStrings', '$log', 'haFeatureFlags', 'haEncryptionService', '$q',
		function (haUtils, haDateUtils, $window, haCitiesSvc, $timeout, haConfig, $scs, $log, flags, encryptionSvc, $q) {

			return {
				templateUrl: haConfig.getTemplateUrl('ha-cartrawler-forms-template.html'),
				restrict: 'A',
				scope: {
					onMauve: '@'
				},
				link: function ($scope, $el, $attrs) {

					var pleaseCorrectErrorsMsg,
						googlePlacesScript = 'https://maps.googleapis.com/maps/api/js?',
						MAX_PASSENGERS = 7;
					googlePlacesScript += 'libraries=places&key=';
					googlePlacesScript += flags.get('GoogleMapApiKey');

					// lazy load Google Places dependency
					haUtils.injectScriptDependency($el, googlePlacesScript);

					$scope.scContent = {};
					$scope.scDiscountContent = {};
					$scope.scBookingContent = {};
					$scope.Error = '';
					$scope.showTabGroup = false;
					// defaults
					$scope.model = {
						transportType: 'car',
						departDate: '',
						returnDate: '',
						originCity: '',
						destinationCity: '',
						onMauve: $scope.onMauve,
						transp_datepicker_config: {
							start: '[name=_TranspFromDate]',
							end: '[name=_TranspToDate]',
							depart_cal_title: '',
							return_cal_title: '',
							namespace: 'cartrawler',
							onRender: function ($cal) {
								if ($scope.tripType === 2) {
									$cal.addClass('double-wide');
								} else {
									$cal.removeClass('double-wide');
								}
							}
						},
						dropOffLocation: '',
						shuttlePassengers: 1,
						pickupTime: '24',
						dropoffTime: '24',
						carPricing: 'dollars'
					};

					// for compatability with ha-datepicker2
					$scope.tripType = 2;
					$scope.legs = [];

					// passengers
					$scope.shuttlePassengers = [];
					for (var i = 1; i <= MAX_PASSENGERS; i++) {
						$scope.shuttlePassengers.push(i);
					}

					// strings
					$scope.encryptionEnabled = false;

					$scs.get('CarTrawler').then(function (data) {
						$scope.scContent = data;
						$scope.carPickUpTimes = $scope.scContent.cartimes.slice();
						$scope.carDropOffTimes = $scope.scContent.cartimes.slice();
						// tab group
						$scope.showTabGroup = ($scope.scContent.enablervs || $scope.scContent.enableshuttles);
						$scope.model.thirdPartyPartnerImage = ($scope.onMauve === 'true')
							? $scope.scContent.bgcarsthirdpartypartnerimage
							: $scope.scContent.nobgcarsthirdpartypartnerimage;

						if ($scope.scContent.enableencryptionforvendorloyalty === "1") {
							$scope.encryptionEnabled = true;
						}
					});
					$scs.get('BookingWidget').then(function (data) {
						$scope.scBookingContent = data;
					 	pleaseCorrectErrorsMsg = data.pleasecorrecttheerrorsbelow;
					});

					//abg discount offer flags
					$scope.showDiscountBanners = false;

					$scs.get('CarRentalDiscountCode').then(function (data) {
						$scope.scDiscountContent = data;

						var banners = $scope.scDiscountContent.discountbanners;

						if (banners) {
							$scope.discountBanners = banners.slice();

							if ($scope.discountBanners && $scope.discountBanners.length > 0) {
								$scope.showDiscountBanners = true;
							}
						}
					});

					// we can stop the spinner on homepage
					$scope.$emit('cartrawlerFormsLoaded');

					$scope.setCarPricing = function (carPricing) {
						$scope.model.carPricing = carPricing;
						$scope.$broadcast('car pricing changed', $scope.model.carPricing);
					}

					// broadcast tripType so calendar can update
					$scope.setTripType = function(tripType) {
						$scope.tripType = tripType;
						$scope.$broadcast('trip type changed', $scope.tripType);
					};

					// need to change tripType back to 2 if not shuttle, and vice versa
					$scope.$watch('model.transportType', function (newValue) {
						if (newValue) {
							if (newValue === 'shuttle') {
								$scope.setTripType(1);
								$scope.model.thirdPartyPartnerImage = $scope.onMauve === 'true'
									? $scope.scContent.bgshuttlesthirdpartypartnerimage
									: $scope.scContent.nobgshuttlesthirdpartypartnerimage;
							} else {
								$scope.setTripType(2);

								if (newValue === 'rv') {
									$scope.model.thirdPartyPartnerImage = $scope.onMauve === 'true'
										? $scope.scContent.bgrvsthirdpartypartnerimage
										: $scope.scContent.nobgrvsthirdpartypartnerimage;
								} else {
									$scope.model.thirdPartyPartnerImage = $scope.onMauve === 'true'
										? $scope.scContent.bgcarsthirdpartypartnerimage
										: $scope.scContent.nobgcarsthirdpartypartnerimage;
								}
								
							}
						}
					});

					// watch for MKK, LNY, or ITO locations on shuttle form
					$scope.$on('airportOrAddressInputChanged', function (event, data) {
						if ($scope.model.transportType === 'shuttle') {

							var valid = true;
							if (data.model.Code && data.model.Code === 'MKK' || data.model.Code === 'LNY' || data.model.Code === 'ITO') {
								valid = false;
							}
							if (data.model.isAddress && data.model.DisplayName.match(/\sLanai\,\s|Lanai\sCity\,|Molokai\,?\s|\sHoolehua\,\s|Ho\'olehua\,?\s|Kaunakakai\,?\s|\sHilo\,?\s/g)) {
								valid = false;
							}

							$scope.form[data.name].$setValidity('location', valid);
							if (!valid) {
								$scope.Error = $scope.scContent.shuttleservicenotavailabletext;
							} else {
								$scope.Error = '';
							}

						}
					});

					$scope.submitButtonText = function() {
						if ($scope.scContent && $scope.scContent.searchcarrentalstext) {
							switch ($scope.model.transportType) {
								case 'shuttle':
									return $scope.scContent.searchshuttlestext;
									break;
								default:
									return $scope.scContent.searchcarrentalstext;
									break;
							}
						}
						else {
							return '';
						}
					};


					// for cars, if days are the same, limit dropoff times to be after pickup time
					$scope.updateDropOffTimes = function () {
						//$log.debug('update drop off times');
						if ($scope.scContent.cartimes && $scope.model.departDate && $scope.model.returnDate) {
							if ($scope.model.departDate.getTime() === $scope.model.returnDate.getTime()) {
								var pickupHour = +$scope.model.pickupTime;
								if (pickupHour !== 24) {
									$scope.carDropOffTimes = $scope.scContent.cartimes.slice(pickupHour + 1);
									if (+$scope.model.dropoffTime <= pickupHour) {
										$scope.model.dropoffTime = String(pickupHour + 1);
									}
								} else {
									$scope.carDropOffTimes = $scope.scContent.cartimes.slice();
								}
							} else {
								$scope.carDropOffTimes = $scope.scContent.cartimes.slice();
							}
						}
					};

					// watches
					$scope.$watch(function (scope) {
						if (scope.model) {
							return scope.model.departDate;
						}
					}, $scope.updateDropOffTimes);

					$scope.$watch(function (scope) {
						if (scope.model) {
							return scope.model.returnDate;
						}
					}, $scope.updateDropOffTimes);

					$scope.$watch(function (scope) {
						if (scope.model) {
							return scope.model.pickupTime;
						}
					}, $scope.updateDropOffTimes);

					// Shuttle time validation when the depart and return dates are the same.
					var shuttleTimeValid = function() {
						// make sure we have dates and that they are the same
						if ($scope.model.departDate && $scope.model.returnDate && $scope.model.departDate.getTime() === $scope.model.returnDate.getTime()) {
							// make sure we have both hours set
							if ($scope.model.pickUpHour && $scope.model.returnHour) {
								if (parseInt($scope.model.pickUpHour) > parseInt($scope.model.returnHour)) {
									// pickUpHour is after returnHour
									return false;
								} else if ($scope.model.pickUpHour === $scope.model.returnHour) {
									// hours are the same
									if ($scope.model.pickUpMinute && $scope.model.returnMinute && parseInt($scope.model.pickUpMinute) >= parseInt($scope.model.returnMinute)) {
										// pickUpMinute is after or the same as returnMinute
										return false;
									}
								}
							}
						}
						return true;
					}
					$scope.$watchGroup(['model.pickUpHour','model.pickUpMinute','model.departDate'], function() {
						if (!shuttleTimeValid()) {
							$scope.model.returnHour = $scope.model.returnMinute = undefined;
						}
					});
					$scope.$watchGroup(['model.returnHour','model.returnMinute','model.returnDate'], function() {
						if (!shuttleTimeValid()) {
							$scope.model.pickUpHour = $scope.model.pickUpMinute = undefined;
						}
					});

					// Prevent duplicate addresses for shuttles
					$scope.$watch('model.pickUpLocation', function(nv) {
						// Check if both values exist
						if (nv && $scope.model.dropOffLocation) {
							// New value is a place, not an airport, so compare placeIds.
							if (nv.placeId && (nv.placeId === $scope.model.dropOffLocation.placeId)) {
								$scope.model.dropOffLocation = undefined;
							// Otherwise compare airport names
							} else if (nv.Name && nv.Name === $scope.model.dropOffLocation.Name) {
								$scope.model.dropOffLocation = undefined;
							}
						}
					});
					$scope.$watch('model.dropOffLocation', function(nv) {
						// Check if both values exist
						if (nv && $scope.model.pickUpLocation) {
							// New value is a place, not an airport, so compare placeIds.
							if (nv.placeId && (nv.placeId === $scope.model.pickUpLocation.placeId)) {
								$scope.model.pickUpLocation = undefined;
							// Otherwise compare airport names
							} else if (nv.Name && nv.Name === $scope.model.pickUpLocation.Name) {
								$scope.model.pickUpLocation = undefined;
							}
						}
					});

					// pre-select flightQuery cookie values
					var flightQueryCookie = haUtils.getFlightQueryModelCookie();
					if (flightQueryCookie) {
						// $log.debug('flight query model cookie');
						// $log.debug(flightQueryCookie);

						if (flightQueryCookie.FlightSearchSegmentList[0]) {

							var originCode = flightQueryCookie.FlightSearchSegmentList[0].OriginCityCode;
							var destinationCode = flightQueryCookie.FlightSearchSegmentList[0].DestinationCityCode;
							// get cities from airport code
							haCitiesSvc.getCityByCode(originCode).then(function (airport) {
								// $log.debug(airport);
								$scope.model.originCity = airport;
							});
							haCitiesSvc.getCityByCode(destinationCode).then(function (airport) {
								$scope.model.destinationCity = airport;
							});

							// dates
							$scope.model.departDate = processDate(flightQueryCookie.FlightSearchSegmentList[0].DepartureDate);
							//$log.debug('set depart date: '+$scope.model.departDate);
						}
						if (flightQueryCookie.FlightSearchSegmentList[1]) {
							$scope.model.returnDate = processDate(flightQueryCookie.FlightSearchSegmentList[1].DepartureDate);
							//$log.debug('set return date: '+$scope.model.returnDate);
						}

					}
					$scope.setForm = function (form) {
						$scope.form = form;
					};

					$scope.submitCarForm = function (e) {
						e.preventDefault();

						if (!$scope.form.$valid) {
							$scope.Error = pleaseCorrectErrorsMsg;
							return;
						}
						//$scope.model.submitted = true;
						$scope.Error = '';

						// submit shuttle form
						if ($scope.model.transportType === 'shuttle') {
							submitShuttleForm();
						}
						// submit car form
						else if ($scope.model.transportType === 'car') {
							submitCarForm();							
						}

					};

					function submitShuttleForm() {
						/* expected params:
							clientID
							residenceID
							curr (currency)
							countryID
							pickupIATACode (if airport was entered in the  form)
							pickupName
							pkLat
							pkLng
							returnIATACode (if airport was entered in the  form)
							returnName (Drop off)
							rtLat
							rtLng
							pickupDateTime (Pickup Date + Pickup Hour + Pickup Minute)
							returnDateTime (Return Date + Return Hour + Return Minute)
							adults (# of passengers)
							oneway (true/false)
							*/
						
						var queryObject;

						var formGetUrl = $scope.scContent.searchshuttlesurl;
						var location = window.location.href.toLowerCase(),
							clientId;
						// determine location for which client ID to use
						if (location.match(/book/i)) {
							clientId = $scope.scContent.cartrawlerclientidbookpage;
						} else {
							clientId = $scope.scContent.cartrawlerclientidhomepage;
						}

						queryObject = {
							clientID: clientId,
							residenceID: $scope.scContent.cartrawlerresidencyidparam,
							curr: $scope.scContent.cartrawlercurrencycodeparam,
							adults: $scope.model.shuttlePassengers,
							oneway: Boolean($scope.tripType === 1),
							pickupName: $scope.model.pickUpLocation.DisplayName
						}
						// pick up params
						var pickUpDate = moment($scope.model.departDate).format('YYYY-MM-DD');
						var pickUpTime = 'T' + timestampFromHourAndMinute($scope.model.pickUpHour, $scope.model.pickUpMinute);
						queryObject.pickupDateTime = pickUpDate + pickUpTime;

						if ($scope.model.pickUpLocation.Code) {
							queryObject.countryID = $scope.model.pickUpLocation.CountryCode;
							queryObject.pickupIATACode = $scope.model.pickUpLocation.Code;
						}
						else if ($scope.model.pickUpLocation.geocode) {
							queryObject.countryID = $scope.model.pickUpLocation.countryCode || 'US';
							queryObject.pkLat = $scope.model.pickUpLocation.geocode.lat;
							queryObject.pkLng = $scope.model.pickUpLocation.geocode.long;
						}
						// drop off params
						if ($scope.model.dropOffLocation && $scope.model.dropOffLocation.DisplayName) {

							queryObject.returnName = $scope.model.dropOffLocation.DisplayName;
							if ($scope.model.dropOffLocation.Code) {
								queryObject.returnIATACode = $scope.model.dropOffLocation.Code;
							}
							else if ($scope.model.dropOffLocation.geocode) {
								queryObject.rtLat = $scope.model.dropOffLocation.geocode.lat;
								queryObject.rtLng = $scope.model.dropOffLocation.geocode.long;
							}

							if ($scope.tripType === 2) {
								var dropOffDate = moment($scope.model.returnDate).format('YYYY-MM-DD');
								var dropOffTime = 'T' + timestampFromHourAndMinute($scope.model.returnHour, $scope.model.returnMinute);
								queryObject.returnDateTime = dropOffDate + dropOffTime;
							}
						}
						
						formGetUrl = getUrl(formGetUrl, queryObject);

						//$log.debug(formGetUrl);
						//$log.debug('query object', queryObject);
						window.location.href = formGetUrl;
					}

					function submitCarForm() {
						var queryObject;
						var carPricing;
						window.digitalData.rentalCarPayment = {};

						if ($scope.model.carPricing.toLowerCase() === "dollars") {
							carPricing = "Dollars";
						}
						else if ($scope.model.carPricing.toLowerCase() === "miles") {
							carPricing = "Miles";
						}
						else {
							carPricing = "Miles/Dollars";
						}

						// event to post ABG Car Booking from Standalone Path
						document.body.dispatchEvent(new CustomEvent('StandaloneCarBooking',  {
							detail: {
								name: 'StandaloneCarBooking',
								timestamp: new Date().getTime(),
								paid: carPricing,
								requestFrom: 'StandaloneBooking'
							}
						}));

						var formGetUrl = $scope.scContent.searchcarrentalsurl;

						var key = "AmazonCarRentalKey";

						var destinationCode = $scope.model.destinationCity.Code;
						var originCode = $scope.model.carDropoff ? $scope.model.carDropoff.Code : destinationCode;

						var pickupHour = $scope.model.pickupTime;
						// based on pickup value of 'Anytime' on hawaiianairlines.com
						if (+pickupHour === 24) {
							pickupHour = 10;
						}

						var returnHour = $scope.model.dropoffTime;
						// based on dropoff value of 'Anytime' on hawaiianairlines.com
						if (+returnHour === 24) {
							returnHour = 11;
						}

						var clientId = getCarClientId();

						queryObject = {
							ops: 'spec',
							srch: 'car',
							eapid: '11428-30001',
							GOTO: 'CARSEARCH',
							lang: $scope.scContent.cartrawlerlanguageparam, // variable param
							rfrr: '-34980',
							age: '30',
							pickupIATACode: destinationCode,
							returnIATACode: originCode,
							pickupYear: $scope.model.departDate instanceof Date ? $scope.model.departDate.getFullYear() : '',
							pickupMonth: $scope.model.departDate instanceof Date ? $scope.model.departDate.getMonth() : '',
							pickupDate: $scope.model.departDate instanceof Date ? $scope.model.departDate.getDate() : '',
							pickupHour: pickupHour,
							pickupMinute: '00',
							returnYear: $scope.model.returnDate instanceof Date ? $scope.model.returnDate.getFullYear() : '',
							returnMonth: $scope.model.returnDate instanceof Date ? $scope.model.returnDate.getMonth() : '',
							returnDate: $scope.model.returnDate instanceof Date ? $scope.model.returnDate.getDate() : '',
							returnHour: returnHour,
							returnMinute: '00',
							currency: $scope.scContent.cartrawlercurrencycodeparam || 'USD',
							residencyId: $scope.scContent.cartrawlerresidencyidparam || 'US',
							c: $scope.scContent.cartrawlerlanguageparam,
							clientID: clientId
						};


						if ($scope.model.carPricing.toLowerCase() === "dollars" && $scope.model.carDiscount && $scope.model.carDiscount.selected && $scope.model.discountCode) {
							var avMembership = $scope.model.discountCode.avisWizard;
							var bgMembership = $scope.model.discountCode.budgetFastbreak;
							var zaMembership = $scope.model.discountCode.paylessPerks;

							if ($scope.encryptionEnabled) {
								var avMembershipPromise = (avMembership && avMembership !== '') ? encryptionSvc.EncryptString(avMembership, key) : getEmptyStringPromise();
								var bgMembershipPromise = (bgMembership && bgMembership !== '') ? encryptionSvc.EncryptString(bgMembership, key) : getEmptyStringPromise();
								var zaMembershipPromise = (zaMembership && zaMembership !== '') ? encryptionSvc.EncryptString(zaMembership, key) : getEmptyStringPromise();
								var discountPromises = [avMembershipPromise, bgMembershipPromise, zaMembershipPromise];

								$q.allSettled(discountPromises)
									.then(function (results) {
										var carRentalQueryObj = getCarRentalQueryObj(results[0].value, results[1].value, results[2].value);

										formGetUrl = getUrl(formGetUrl, queryObject, carRentalQueryObj);

										//$log.debug(formGetUrl);
										//$log.debug('query object', queryObject);
										window.location.href = formGetUrl;
									})
							} else {
								var carRentalQueryObj = getCarRentalQueryObj(avMembership, bgMembership, zaMembership);

								formGetUrl = getUrl(formGetUrl, queryObject, carRentalQueryObj);
								//$log.debug(formGetUrl);
								//$log.debug('query object', queryObject);
								window.location.href = formGetUrl;
							}
						} else {

							formGetUrl = getUrl(formGetUrl, queryObject);

							//$log.debug(formGetUrl);
							//$log.debug('query object', queryObject);
							window.location.href = formGetUrl;
						}
					}

					function getCarClientId() {
						var location = window.location.href.toLowerCase(),
						clientId;

						var isDollars = ($scope.model.carPricing && $scope.model.carPricing.toLowerCase() === 'dollars');
						var isBookPage = location.match(/book/i);

						// determine location for which client ID to use
						if (isBookPage) {
							if (isDollars) {
								clientId = $scope.scContent.cartrawlercarsclientidbookpage;
							} else {
								clientId = $scope.scContent.cartrawlercarsclientidmilesbook;
							}
						} else {
							if (isDollars) {
								clientId = $scope.scContent.cartrawlercarsclientidhomepage;
							} else {
								clientId = $scope.scContent.cartrawlercarsclientidmileshome;
							}
						}

						return clientId;
					}

					function getEmptyStringPromise() {
						return new Promise(function (resolve) {
							resolve("");
						});
					}

					function getCarRentalQueryObj(avMembership, bgMembership, zaMembership) {
						var carRentalQueryObj = {
							//avis
							AV: {
								CORPORATE_RATE: $scope.model.discountCode.awd,
								MEMBERSHIP_NUMBER: avMembership,
								PROMOTIONAL_CODE: $scope.model.discountCode.avisCoupon
							},
							//budget
							BG: {
								CORPORATE_RATE: $scope.model.discountCode.bcd,
								MEMBERSHIP_NUMBER: bgMembership,
								PROMOTIONAL_CODE: $scope.model.discountCode.budgetCoupon
							},
							//payless 
							ZA: {
								CORPORATE_RATE: $scope.model.discountCode.pdn,
								MEMBERSHIP_NUMBER: zaMembership,
								PROMOTIONAL_CODE: $scope.model.discountCode.paylessCoupon
							}
						}
						return carRentalQueryObj;
					}

					function getUrl(formGetUrl, queryObject, carRentalQueryObj) {
						var urlInfo = haUtils.splitUrl(formGetUrl);

						if (urlInfo.params.length > 0) {
							formGetUrl = urlInfo.url;
							formGetUrl += haUtils.createQueryString(queryObject, carRentalQueryObj, urlInfo.params);
						}
						else {
							formGetUrl += haUtils.createQueryString(queryObject, carRentalQueryObj);
						}

						return formGetUrl;
					}

					function processDate(date) {
						var dateString = date.substr(0, 10);
						var parts = dateString.split('-');
						return new Date(parts[0], parts[1] - 1, parts[2]);
					}

					function timestampFromHourAndMinute(hour, minute) {
						var hourInt = parseInt(hour);
						if (hour.indexOf('pm') > -1 && hourInt !== 12) {
							hourInt += 12;
						}
						if (hourInt === 12 && hour.indexOf('am') > -1) {
							hourInt = 0;
						}
						hour = '0'+hourInt;
						return hour.slice(-2) + ':' + minute + ':00';
					}
				}
			};
		}]);

})(angular);
;
(function (angular) {

	// Ha Primary Nav Account Menu
	// --------------------------------------------
	//
	// * **Class:** HaPrimaryNavAccountMenu
	// * **Author:** Cory Shaw
	//
	// Makes an ajax call to an endpoint that returns cached html to improve the backend performance of the global header

	'use strict';

	var module = angular.module('haPrimaryNavAccountMenuModule', []);

	module.directive('haPrimaryNavAccountMenu', ['haHttpService', 'haConfig', '$compile', '$timeout', 'haGlobals', '$q', '$rootScope', function ($http, haConfig, $compile, $timeout, haGlobals, $q, $rootScope) {
		var HaPrimaryNavAccountMenuLink = function ($scope, $el, $attrs) {

			// set theme (coloration)
			$scope.theme = $attrs.theme;

			// Logged out
			if (window.returnUrlQs !== undefined && !$rootScope.isMobile) {

				$scope.retURL = window.returnUrlQs;

				// work around for known issue where scs-* directives don't fetch string in time
				$scs.get('Header.SignInText').then(function (content) {
					$scope.signInText = content;
				});

				$scs.get('Header.JoinNowText').then(function (content) {
					$scope.joinNowText = content;
				});

				$scope.navLoggedOut = true;

				return;
			}

			var accountMenuEndpoint = '/Header/GetMyAccountMenu/';
			var canceller = $q.defer();
			window.onunload = function() {
				canceller.resolve();
			};
			haGlobals('areaName', function (areaName) {
				if (areaName) {
					accountMenuEndpoint += '?area=' + areaName;
				}
			});
			$http.GET(accountMenuEndpoint, { timeout: canceller.promise }).success(function (markup) {
				// note, the markup that is returned in this endpoint can be found in
				// /Areas/Shared/Views/Renderings/Header/MyAccountMenu.cshtml for Desktop
				// /Areas/Shared/Views/Renderings/Header/MyAccountMenuMobile.cshtml for Mobile

				$el.replaceWith($compile(markup)($scope));

				$timeout(function() {
					haGlobals(['registrationUrl'], function (registrationUrl) {
						$scope.$root.registrationUrl = registrationUrl;
					});
				}, 0);
			}).error(function () {
				console.error('haPrimaryNavAccountMenu ajax call to /Header/GetMyAccountMenu/ failed');
			});

			$scope.toggleAccountMenu = function () {
				if ($scope.accountMenuIsOpen) {
					toggleAccountMenu(false);
				} else {
					toggleAccountMenu(true);
				}
			};

			$scope.toggleMyTripsMenu = function () {
				if ($scope.myTripsMenuIsOpen) {
					toggleMyTripsMenu(false);
				} else {
					toggleMyTripsMenu(true);
				}
			};

			function toggleAccountMenu(open) {
				$scope.accountMenuIsOpen = open;
			}

			function toggleMyTripsMenu(open) {
				$scope.myTripsMenuIsOpen = open;
			}

			if (!$scope.$root.isMobile) {
				$('html')
					.on('click',
						function(e) {
							if ($scope.myTripsMenuIsOpen && !$(e.target).closest('.parent').hasClass('my-trips')) {
								$timeout(function() {
									toggleMyTripsMenu(false);
								});
							}

							if ($scope.accountMenuIsOpen && !$(e.target).closest('.parent').hasClass('my-account')) {
								$timeout(function() {
									toggleAccountMenu(false);
								});
							}
						});

				$('body')
					.delegate('a:not(.my-account)',
						'focus',
						function() {
							if ($scope.accountMenuIsOpen) {
								$timeout(function() {
									toggleAccountMenu(false);
								});
							}
						})
					.delegate('a:not(.my-trips)',
						'focus',
						function() {
							if ($scope.myTripsMenuIsOpen) {
								$timeout(function() {
									toggleMyTripsMenu(false);
								});
							}
						})

				$('body')
					.on('keyup',
						function(e) { // -- escape key
							if (e.keyCode === 27) {
								e.preventDefault();
								if ($scope.myTripsMenuIsOpen) {
									$timeout(function() {
										$('a.my-trips').first().focus();
										toggleMyTripsMenu(false);
									});
								}
								if ($scope.accountMenuIsOpen) {
									$timeout(function() {
										$('a.my-account').first().focus();
										toggleAccountMenu(false);
									});
								}
							}
						});
			}

		};

		return {
			restrict: 'A',
			scope: true,
			link: HaPrimaryNavAccountMenuLink,
			templateUrl: haConfig.getTemplateUrl('ha-primary-nav-account-menu.html') // default logged-out markup; will get replaced for auth'd users, as seen above
		};
	}]);

})(angular);
;
(function (angular) {

    // Ha Primary Nav Alerts
    // --------------------------------------------
    //
    // * **Class:** HaPrimaryNavAlerts
    // * **Author:** Cory Shaw
    //
    // Makes an ajax call to an endpoint that returns cached html to improve the backend performance of the global header

    'use strict';

    var module = angular.module('haPrimaryNavAlertsModule', []);

    module.directive('haPrimaryNavAlerts', ['haHttpService', '$compile', '$location', function ($http, $compile, $location) {
        var HaPrimaryNavAlertsLink = function ($scope, $el) {
            var url = $location.absUrl();
            var queryString = url.indexOf("?") > -1 ? url.split("?")[1] : "";
            var alertsEndpoint = '/Header/GetAlerts/';
            if (queryString !== "") {
                alertsEndpoint += "?" + queryString;
            }
            $http.GET(alertsEndpoint).success(function (markup) {
                // note, the markup that is returned in this endpoint can be found in
                // /Areas/Shared/Views/Renderings/Header/Alerts.cshtml

                $el.replaceWith($compile(markup)($scope));
            }).error(function () {
                console.error('haPrimaryNavAlerts ajax call to /Header/GetAlerts/ failed');
            });

        };

        return {
            restrict: 'A',
            scope: true,
            link: HaPrimaryNavAlertsLink
        };
    }]);

})(angular);
;
(function () {

	'use strict';

	function bodyController($scope, $rootScope, haConfig, $interval, $q, haModal, $scs, haSessionTimeoutApi, $location, $log) {

		// Session handling
		initializeSession($scope, $rootScope, haConfig, $interval, $q, haModal, $scs, haSessionTimeoutApi, $location, $log);

		// Initialize ARIA injector
		initializeAriaInjector($scs, $log);

	}

	function initializeAriaInjector($scs, $log) {
		$scs.get('Accessibility').then(function (data) {
			var regex = data.externallinkregex,
				subDomainRegex = data.subdomainexcludes,
				selector = data.cssselector,
				message = data.externallinkmessage,
				re,
				subdomainRe;


			try {
				re = new RegExp(regex, 'i');
				subdomainRe = new RegExp(subDomainRegex, 'i');
			} catch (err) {
				$log.error('invalid regex from sitecore for accessibility');
			return;
		}

			var $elements = $(selector);
			if (!$elements.length) { return; }

			$elements.each(function () {
				var $link = $(this),
					href = $link.prop('href');

				if (!href || (re.test(href) && !subdomainRe.test(href))) {
					return;
				}

				$link.append($('<span class="sr-only">{0}</span>'.format(message)));
			});
		});
	}

	function initializeSession($scope, $rootScope, haConfig, $interval, $q, haModal, $scs, haSessionTimeoutApi, $location, $log) {

		var sessionIsOn = $rootScope.$switch('Global:EnableSessionTimeout'),
			sessionIsOnBookingPath = $rootScope.$switch('Global:EnableSessionTimeoutBookingPath'),
			sessionIsOnMyAccount = $rootScope.$switch('Global:EnableSessionTimeoutMyAccount'),
			isBookingPath = /^\/book/i.test(location.pathname),
			isMyAccount = /^\/my-account/i.test(location.pathname);

		$scope.showGlobalHeader = true;

		// Is it turned on or homepage
		if (!sessionIsOn || (isBookingPath && !sessionIsOnBookingPath) || (isMyAccount && !sessionIsOnMyAccount) || /^\/$/.test(location.pathname)) {
			return;
		}

		// Booking Path Exceptions
		if (isBookingPath) {
			if (/\/flights|\/home|\/activities-and-cruises|\/hotels|\/car-rentals|\/vacation-packages|\/confirmation|\/expertbooking/i.test(location.pathname)) {
				return;
			} else {
				$scope.showGlobalHeader = false;
			}
		}

		// My-Account Path Exceptions
		if (isMyAccount) {
			if (/\/login|\/hawaiianmiles|\/join-hawaiianmiles/i.test(location.pathname)) {
				return;
			}
		}

		var stop,
			timerRunning = true,
			modalSessionRemainingTime,
			startOverMyAccount = '/book/shared/startover?redirect=myacount',
			startOverBookHome = '/book/shared/startover?redirect=home',
			startOverFlightHome = '/Book/Home/Index',
			myAccountUrl = '/my-account';

		$scope.userTimeLeft = 0;
		$scope.myAccountAccess = '';

		// 20 minutes by default
		$scope.userTimeLeft = 1200000;

		// Booking path or my account
		if (isBookingPath || isMyAccount) {
			$scs.get('SessionExpiration').then(function (data) {
				modalSessionRemainingTime = parseInt(data.sessionseconds, 10) * 1000;
				stop = $interval(intervalCounter, 1000, 100000, false);
			});
		}

		$scope.showSessionTimeoutWarningModal = function () {
			haModal(haConfig.getTemplateUrl('ha-session-timeout-warning.html'), {
				id: 'session-timeout-modal',
				backdrop: 'true',
				modalLock: true,
				scope: $scope,
				extendScope: { userTimeLeft: $scope.userTimeLeft },
				size: 'modal-md'
			});
		};

		$scope.stopSessionCounter = function () {
			if (angular.isDefined(stop)) {
				$interval.cancel(stop);
				stop = undefined;
				timerRunning = false;
			}
		};

		$scope.resumeSession = function () {
			return restartSessionTime().then(function (response) {
				if (angular.isDefined($scope.$modalCancel)) $scope.$modalCancel();
			});
		}

		$scope.$on('$destroy', function () {
			// Make sure that the interval is destroyed
			$scope.stopSessionCounter();
		});

		$rootScope.$on('$locationChangeStart', function (e, newUrl, oldUrl) {
			// Bail out if the referrer was not a hashed url
			if (!/\#/.test(oldUrl)) {
				return;
			}
			$scope.myAccountAccess = window.location.href.search('dashboard');
			if (timerRunning) {
				restartSessionTime();
			}
		});

		$scope.accountPageRedirect = function () {
			$scope.isLoggedIn = true;
			window.location.href = myAccountUrl;
		};

		$scope.resetSession = function () {
			window.location.href = (window.location.pathname === myAccountUrl) ? startOverMyAccount : startOverBookHome;
		};

		$scope.restartBooking = function () {
			window.location.href = startOverFlightHome;
		};

		function intervalCounter() {
			if ($scope.userTimeLeft > modalSessionRemainingTime) {
				$scope.myAccountAccess = window.location.href.search('dashboard');
				$scope.userTimeLeft -= 1000;
			}
			else if ($scope.userTimeLeft === modalSessionRemainingTime) {
				$scope.showSessionTimeoutWarningModal();
				$scope.userTimeLeft -= 1000;
			}
			else if ($scope.userTimeLeft <= modalSessionRemainingTime && $scope.userTimeLeft > 0) {
				$scope.userTimeLeft -= 1000;
			}
			else {
				$scope.stopSessionCounter();
			}
			$scope.$digest();
		}

		function redirectToErrorPage(errorCode) {
			window.location.href = '/Book/Error?ErrorCode=' + errorCode;
		}

		function restartSessionTime() {
			return haSessionTimeoutApi.restartUserSessionTime()
				.then(function (response) {
					if ( !response || response.status !== 200 || !response.data || isNaN(+response.data) ) {
						redirectToErrorPage('SessionExtendFailed');
					} else {
						$scope.userTimeLeft = +response.data * 1000 * 60;
					}
				})
				.catch(function (error) {
					$log.error('ExtendSession API call Failed: ', error);
					//redirectToErrorPage('SessionExtendFailed');
					// Cancel the redirect here as this causes much BK100 traffic from BOTS
				});
		};
	}

	angular.module('haBodyExtensionModule', []).directive('body', function () {
		return {
			restrict: 'E',
			scope: true,
			controller: bodyController
		};
	});

	bodyController.$inject = ['$scope', '$rootScope', 'haConfig', '$interval', '$q', 'haModal', 'haSitecoreStrings', 'haSessionTimeoutAPI', '$location', '$log'];

})();
;
(function (angular) {

	'use strict';

	// Aiport or Address Type-ahead input
	// ===================================
	//
	// * **Class:** haAirportOrAddressInputModule
	// * **Author:** Jamie Perkins
	//
	// Type-ahead input which autocompletes airports and addresses
	//
	// ---- IMPORTANT: DEPENDENCY ----
	// you must use haUtils.injectScriptDependency() in a parent directive to load the following script:
	// https://maps.googleapis.com/maps/api/js?key=AIzaSyDhFgUSHBggq68VqQGtWJyhpFY2g-ouiV4&libraries=places

	var module = angular.module('haAirportOrAddressInputModule', ['haCitiesModule', 'ui.bootstrap.typeahead.ha']);

	module.directive('haAirportOrAddressInput', ['haConfig', '$timeout', 'haCitiesSvc', '$q', '$parse', '$log',
		function (haConfig, $timeout, haCitiesSvc, $q, $parse, $log) {

		return {
			templateUrl: haConfig.getTemplateUrl('ha-airport-or-address-input-template.html'),
			restrict: 'A',
			scope: {
				ngModel: '=',
				name: '@',
				label: '@',
				placeholder: '@',
				required: '@',
				disabled: '@'
			},
			link: function ($scope, $el, $attrs) {

				var autocompleteService,
					placesService,
					bounds,
					geocodeService;

				function handleError(errMsg) {
					$log.error(errMsg);
					$scope.Error = errMsg;
				}

				// google services defined by dependency script

				function defineGoogleServices() {
					if (google && google.maps && google.maps.places) {
						autocompleteService = new google.maps.places.AutocompleteService();
						geocodeService = new google.maps.Geocoder();
						placesService = new google.maps.places.PlacesService($('#'+$scope.name+'-details')[0]);
						// rough rectangle around where HA flies to restrict results
						bounds = {
							east: -66.828,
							north: 49.0324,
							south: -39.4678,
							west: 100.0187,
						};
						$scope.Error = '';
					} else {
						handleError('Google API service not yet loaded or is not linked');
					}
				}

				function getPlacesResultsForInput(userInput) {
					var deferred = $q.defer();

					if (!autocompleteService) {
						defineGoogleServices();
					}
					autocompleteService.getPlacePredictions({ input: userInput, bounds: bounds/*, types: ['address'] */}, function (predictions, status) {

						if (status !== google.maps.places.PlacesServiceStatus.OK &&
							status !== google.maps.places.PlacesServiceStatus.ZERO_RESULTS) {
							deferred.reject('Google Places API status: '+status);
						}
						var result = [];
						if (predictions && predictions.length) {
							result = predictions.slice();
						}
						deferred.resolve(result);
					});
					return deferred.promise;
				}

				function getCountryCodeFromGeocodedResults(results) {
					for (var i = 0, len = results.address_components.length; i < len; i++) {
						var component = results.address_components[i];
						for (var j = 0, jlen = component.types.length; j < len; j++) {
							if (component.types[j] === 'country') {
								return component.short_name;
							}
						}
					}
					return '';
				}

				// cities

				function getAirportResultsForInput(userInput) {
					return haCitiesSvc.getMatchingCities(userInput);
				}

				// handling results

				$scope.getComboResultsForInput = function(userInput) {
					var deferred = $q.defer();

					$q.all([getAirportResultsForInput(userInput), getPlacesResultsForInput(userInput)]).then(function(resolved) {

						var result = [],
							airports = resolved[0],
							places = resolved[1];

						if (airports.length) {
							airports[0].firstAirportResult = true; // for section header
							result = result.concat(airports);
						}
						if (places.length) {
							var cleanPlaces = [];
							// construct new array to only use properties we need
							for (var i = 0, len = places.length; i < len; i++) {
								cleanPlaces.push({
									DisplayName: places[i].description,
									isAddress: true,
									placeId: places[i].place_id
								});
							}
							cleanPlaces[0].firstPlacesResult = true; // for section header
							cleanPlaces[0].isFromGoogle = true;
							result = result.concat(cleanPlaces);
						}

						deferred.resolve(result);

					}, function(error) {
						deferred.reject(error);
					});

					return deferred.promise;
				};

				$scope.onSelect = function () {

					if ($scope.ngModel.isAddress) {
						// get lat and long
						placesService.getDetails({ placeId: $scope.ngModel.placeId }, function(results, status) {
							//$log.debug('place details', status, results);
							if (status === 'OK') {
								var lat = results.geometry.location.lat(),
									long = results.geometry.location.lng();
								$scope.ngModel.geocode = {
									lat: lat,
									long: long,
								};
								$scope.ngModel.countryCode = getCountryCodeFromGeocodedResults(results);
							} else {
								handleError('PlacesService error:', status);
							}
						});

						/*  not using geocode service bc it can't find many addresses
							even though they came from google
						geocodeService.geocode({ address: $scope.ngModel.DisplayName }, function(results, status) {
							if (status === 'OK' && results.length) {
								var lat = results[0].geometry.location.lat(),
									long = results[0].geometry.location.lng();
								$scope.ngModel.geocode = {
									lat: lat,
									long: long,
								};
								$scope.ngModel.countryCode = getCountryCodeFromGeocodedResults(results);
							} else {
								handleError('geocoding error:', status);
							}
						});*/
					}
					$el.find('input[type="text"]').focus();

					$scope.$emit('airportOrAddressInputChanged', {
						model: $scope.ngModel,
						name: $scope.name
					});
				};

				$scope.onFocus = function() {
					// scroll input to top on mobile
					if ($scope.$root.isMobile) {
						$('html, body').animate({scrollTop: $el.offset().top - 10});
					}
				};
			}
		}
	}]);


})(angular);
;
(function (angular) {

	'use strict';

	// Hotels Aiport or Address Type-ahead input
	// ===================================
	//
    // * **Class:** haHotelsInputModule
	// * **Author:** Mark Hagelberg
	//
	// Type-ahead input which autocompletes airports and cities with Expedia typeahead API

	var module = angular.module('haHotelsInputModule', ['haCitiesModule', 'ui.bootstrap.typeahead.ha']);

	module.directive('haHotelsInput', ['haConfig', 'haUtils', 'haCitiesSvc', '$q', '$log', '$timeout',
		function (haConfig, haUtils, haCitiesSvc, $q, $log, $timeout) {

		return {
			templateUrl: haConfig.getTemplateUrl('ha-hotels-input-template.html'),
			restrict: 'A',
			scope: {
				ngModel: '=',
				name: '@',
				label: '@',
				placeholder: '@',
				required: '@',
				disabled: '@'
			},
			link: function ($scope, $el, $attrs) {
	
				function handleError(errMsg) {
					$log.error(errMsg);
					$scope.Error = errMsg;
				}

			    // Get Expedia suggest results
				function getPlacesResultsForInput(userInput) {
				    return haCitiesSvc.getHotelCities(userInput);
				}

			    // airports
				function getAirportResultsForInput(userInput) {
					return haCitiesSvc.getMatchingCities(userInput);
				}

			    // handling results
				$scope.getComboResultsForInput = function(userInput) {
				    var deferred = $q.defer();

					$q.all([getAirportResultsForInput(userInput), getPlacesResultsForInput(userInput)]).then(function(resolved) {

						var result = [],
							airports = resolved[0],
							places = resolved[1];

						if (airports && airports.length) {
						    airports = haUtils.removeDuplicatesFromArray(airports, 'DisplayName').slice(0, 5);
						    var cleanAirports = [];
						    // construct new array to only use properties we need
						    for (var i = 0, len = airports.length; i < len; i++) {
						        cleanAirports.push({
						            DisplayName: airports[i].DisplayName,
						            IsHACity: airports[i].IsHACity,
						            Code: airports[i].Code
						        });
						    }
						    cleanAirports[0].firstAirportResult = true; // for section header
						    result = result.concat(cleanAirports);
						}
						if (places && places.length) {
						    var cleanPlaces = [];
						    // construct new array to only use properties we need
							for (var i = 0, len = places.length; i < len; i++) {
								cleanPlaces.push({
								    DisplayName: places[i].regionNames.fullName,
									isAddress: true
								});
							}
							cleanPlaces = haUtils.removeDuplicatesFromArray(cleanPlaces, 'DisplayName');
							cleanPlaces[0].firstPlacesResult = true; // for section header
							result = result.concat(cleanPlaces);
						}

						deferred.resolve(result);

					}, function (error) {
					    handleError(error);
						deferred.reject(error);
					});

					return deferred.promise;
				};
				
				// fires when coming from modal window
				$scope.onSelected = function (city) {
					$scope.ngModel = city;
					$timeout(function () {
						$scope.focus();
					}, 0);
				};

				// fires when clicking or tabbing a highlighted result
				$scope.onSelect = function () {
					$timeout(function () {
						$scope.focus();
					}, 0);
				};

				$scope.onClick = function ($event) {
					// select input value on click
					$event.target.select();
				};

				$scope.onFocus = function() {
					// scroll input to top on mobile
					if ($scope.$root.isMobile) {
						$('html, body').animate({scrollTop: $el.offset().top - 10});
					}
				};

				$scope.pinClicked = function () {
					$scope.$emit('haWhereWeFlyPinClicked');
				};

				$scope.focus = function () {
					setTimeout(function () {
						$el.find('input[type="text"]').focus();
					}, 0);
				};
			}
		}
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	// Hawaiian RV form
	// ===============================
	//
	// * **Class:** HaRvForm
	// * **Author:** Jamie Perkins
	//
	// Forms to hand user off to Motorhome Replubic.
	// Depends on inheriting ha-cartrawler-forms.js scope

	var module = angular.module('haRVFormModule', []);

	module.directive('haRvForm', ['haConfig', 'haUtils', '$log', '$http',
		function (haConfig, haUtils, $log, $http) {

			return {
				templateUrl: haConfig.getTemplateUrl('ha-rv-form.html'),
				restrict: 'A',
				link: function ($scope, $el) {
					var MAX_RV_DRIVER_AGE = 100,
						MIN_RV_DRIVER_AGE_US = 21,
						MIN_RV_DRIVER_AGE = 18;

					// extend inherited model, if necessary
					if (!$scope.model.rvCountries) {
						$scope.model.rvCountries = [];
						$scope.model.rvCities = [];
						$scope.model.rvPickUp = '';
						$scope.model.rvDropOffDisplay = 'SAME';
						$scope.model.rvDropOff = '';
						$scope.model.driverAge = '';
						$scope.model.countryOfResidence = 'US';
					}
					$scope.model.thirdPartyPartnerImage = $scope.onMauve === 'true'
						? $scope.scContent.bgrvsthirdpartypartnerimage
						: $scope.scContent.nobgrvsthirdpartypartnerimage;
					// driver ages
					$scope.driverAgesUS = [];
					for (var a = MIN_RV_DRIVER_AGE_US; a <= MAX_RV_DRIVER_AGE; a++) {
						$scope.driverAgesUS.push(a);
					}
					$scope.driverAgesElsewhere = [];
					for (var a = MIN_RV_DRIVER_AGE; a <= MAX_RV_DRIVER_AGE; a++) {
						$scope.driverAgesElsewhere.push(a);
					}

					// watch country to change driver age range
					$scope.$watch('model.rvCountry', function (newValue) {
						if (newValue && newValue === 'US') {
							$scope.driverAges = $scope.driverAgesUS;
							$scope.driverAgeLabel = $scope.scContent.driverageustext;
						} else {
							$scope.driverAges = $scope.driverAgesElsewhere;
							$scope.driverAgeLabel = $scope.scContent.driveragetext;
						}
						if (newValue) {
							$scope.model.rvDropOffDisplay = 'SAME';
						}
					});

					// set country from city list if it is not set yet
					$scope.$watch('model.rvPickUp', function (newValue) {
						if (newValue && !$scope.model.rvCountry) {
							// find country of chosen city and set country renting from
							for (var i = 0, len = $scope.model.rvCities.length; i < len; i++) {
								if ($scope.model.rvCities[i].value === newValue) {
									$scope.model.rvCountry = $scope.model.rvCities[i].countryCode;
									break;
								}
							}
						}
						if (newValue) {
							// set dropoff location if set to SAME
							if ($scope.model.rvDropOffDisplay === 'SAME') {
								$scope.model.rvDropOff = newValue;
							}
						}
					});

					/*  actual and display values exist because a location name
						must be present for dropoff_location. "Same" or empty are
						not valid values.
					*/
					$scope.$watch('model.rvDropOffDisplay', function (newValue) {
						if (newValue && newValue !== 'SAME') {
							$scope.model.rvDropOff = newValue;
						}
						else if (newValue && newValue === 'SAME') {
							if ($scope.model.rvPickUp) {
								$scope.model.rvDropOff = $scope.model.rvPickUp;
							}
						}
					});

					function countryPredicate (rvCity) {
						if (rvCity.countryCode === '') {
							return true;
						}
						if ($scope.model.rvCountry && $scope.model.rvCountry.length) {
							if (rvCity.countryCode === $scope.model.rvCountry) {
								return true;
							}
							else {
								return false;
							}
						}
						else {
							return true;
						}
					}

					/*	have to return an array with this function because IE does
						not support filter functions in ng-options */
					$scope.citiesForSelectedCountry = function(isReturn) {
						if ($scope.model.rvDestCities && $scope.model.rvReturnCities) {
							if (isReturn) {
								return $scope.model.rvReturnCities.filter(countryPredicate);
							} else {
								return $scope.model.rvDestCities.filter(countryPredicate);
							}
						} else {
							return [];
						}

					}

					function handleError(errMsg) {
						//$scope.Error = errMsg;
						$log.error(errMsg);
					}

					function getRVCountryCityList() {
						// handle errors
						if (!$scope.scContent.countrycitylisturl) {
							handleError('Missing URL for RV country and city list');
							return;
						}

						// load country-city list and establish dropdown options
						$http.get($scope.scContent.countrycitylisturl, {
		                    cache: true
						}).then(function(response) {
							if (response && response.data) {
								var locations = response.data;
								var allowedCountries = ['AU', 'NZ', 'US'];
								var countries = [];
								for (var i = 0, len = locations.length; i < len; i++) {
									if (allowedCountries.indexOf(locations[i].CountryCode) >= 0) {
										$scope.model.rvCities.push({
											name: locations[i].Name +', '+ locations[i].State,
											value: locations[i].Name,
											country: locations[i].CountryName,
											countryCode: locations[i].CountryCode
										});
										if (countries.indexOf(locations[i].CountryCode) < 0) {
											$scope.model.rvCountries.push({
												name: locations[i].CountryName,
												code: locations[i].CountryCode
											});
											countries.push(locations[i].CountryCode);
										}
									}
								}
								$scope.model.rvDestCities = $scope.model.rvCities.slice();
								$scope.model.rvDestCities.unshift({
									name: $scope.scContent.rvlocationplaceholdertext,
									value: '',
									countryCode: ''
								});
								$scope.model.rvReturnCities = $scope.model.rvCities.slice();
								$scope.model.rvReturnCities.unshift({
									name: $scope.scContent.sameaspickuptext,
									value: 'SAME',
									countryCode: ''
								});
							}
							else {
								handleError('Error getting RV cities: incomplete response');
							}
						}, function (error) {
							handleError('Error getting RV cities: '+error);
						});
					}

					// init
					if ($scope.model.rvCountries.length === 0) {
						getRVCountryCityList();
					}
				}
			};

		}]);

})(angular);
;
(function (angular) {
	'use strict';

	var mod = angular.module('haPairLinksModule', []);
	mod.directive('pairLinksMenu', ['$window', '$timeout', function ($window, $timeout) {
		return {
			restrict: 'A',
			scope: true,
			link: function ($scope, $el, $attrs, $ctrl) {
				var selectA = $el.find('select').first();
				var selectB = $el.find('select').last();
				var formController;

				$attrs.$observe('name', function(val) {
					formController = $scope[val];
				});
				$attrs.$observe('listA', function(val) {
					val = JSON.parse(val);
					selectA.html(buildSelect(val));
					$scope.listA = val;
				});
				$attrs.$observe('listB', function(val) {
					val = JSON.parse(val);
					selectB.html(buildSelect(val));
					$scope.listB = val;
				});
				$attrs.$observe('pairs', function(val) {
					$scope.pairs = JSON.parse(val);
					$scope.pairMap = buildPairMap($scope.pairs);
				});
				$attrs.$observe('defaultA', function(val) {
					$timeout(function() {
						selectA.val(val.Id).change();
					});
				});
				$attrs.$observe('defaultB', function(val) {
					$timeout(function() {
						selectB.val(val.Id).change();
					});
				});

				$scope.$watchGroup(['itemA', 'itemB'], function(nv) {
					formController.itemA.$setValidity('notfound', true);
					formController.itemB.$setValidity('notfound', true);
				});
				$scope.go = function(event) {
					event.preventDefault();
					var link = $scope.pairMap[$scope.itemA + $scope.itemB];
					if (link && link.Url) {
						// link pair found
						var url = link.Url;
						var target = link.Target;
						$window.open(url, (target || '_self'));
					} else {
						formController.itemA.$setValidity('notfound', false);
						formController.itemB.$setValidity('notfound', false);
					}
				};

				function buildPairMap(pairs) {
					var pairMap = {};
					angular.forEach(pairs, function(val) {
						pairMap[val.ItemA + val.ItemB] = val.Link;
					});
					return pairMap;
				}
				function buildSelect(list) {
					var listMarkup = '';
					var inGroup = false;
					angular.forEach(list, function(val, key) {
						if ((val.IsGroupHeader || val.IsTopLevel) && inGroup) {
							listMarkup += '</optgroup>';
							inGroup = false;
						}
						if (val.IsGroupHeader) {
							listMarkup += '<optgroup label="' + val.Label + '">';
							inGroup = true;
						} else {
							listMarkup += '<option value="' + val.Id + '">' + val.Label + '</option>';
						}
					});
					if (inGroup) {
						listMarkup += '</optgroup>';
						inGroup = false;
					}
					return listMarkup;
				}
			}
		};
	}]);
})(angular);
;
(function (angular) {

	// HA Name
	// --------------------------------------------
	//
	// * **Class:** haName
	// * **Author:** Nathan Probst
	//
	// A directive to allow interpolation of name attributes on form inputs

	'use strict';

	var module = angular.module('haName', []);

	// NEP: TODO: Move this.
	module.directive('haName', ['$compile', '$interpolate', function ($compile, $interpolate) {
		return {
			restrict: 'A',
			terminal: true,
			priority: 100000,
			scope: false,
			link: function ($scope, $el) {
				var attr = $el.attr('ha-name');
				var name = $interpolate(attr, false, null, true)($scope);
				$el.removeAttr('ha-name');
				$el.attr('name', name);
				$compile($el)($scope);
			}
		};
	}]);

})(angular);
;
(function (angular) {

	// @scniro

	'use strict';

	var module = angular.module('haCustomDropdownModule', []);

	module.directive('haCustomDropdown', ['haConfig', '$timeout', function (haConfig, $timeout) {
		return {
			restrict: 'A',
			scope: {
				label: '@'
			},
			transclude: true,
			templateUrl: haConfig.getTemplateUrl('ha-custom-dropdown-base-template.html'),
			link: function (scope, elem) {

				var sender = angular.element([]); // empty element selector. guard against possible undefined.focus(); error

				function onFocus(event) {
					if (!elem.has(event.target).length) {
						$timeout(scope.customDropdownDeactivate);

						if (!$(event.target).is(':focusable')) {
							// focus to sender when target is non-focusable e.g. body, <div> etc. -- sample use case: allow expected focus shift to <input /> element. jQuery UI dependency.
							sender.focus();
						}
					}
				}

				function onKey(event) {
					if (event.keyCode === 27) { // esc key
						event.stopPropagation();
						$timeout(scope.customDropdownDeactivate);
						sender.focus();
					}
				}

				scope.customDropdownActivate = function () {
					angular.element('body')
					.on('focusin touchstart click', onFocus); // attach locally scoped handlers

					angular.element(elem)
					.on('keydown', onKey);

					scope.active = true;
				};

				scope.customDropdownDeactivate = function () {
					angular.element('body')
					.off('focusin touchstart click', onFocus); // detach locally scoped handlers

					angular.element(elem)
					.off('keydown', onKey);

					scope.active = false;
				};

				scope.toggleCustomDropdownActive = function ($event) {
					sender = angular.element($event.target);
					if (scope.active) {
						scope.customDropdownDeactivate();
					} else {
						scope.customDropdownActivate();
					}
				};

				scope.$on('$closeCustomDropdown', function () {
					sender.focus(); // focus on custom close action e.g. avatar selection <button ng-click="closeAvatarSelector()">done</button>
					scope.customDropdownDeactivate();
				});

				scope.$on('$openCustomDropdown', function () {
					scope.customDropdownActivate();
				});
			}
		};
	}]);
})(angular);
;
(function (angular) {

	// Ha Form Validation Directive
	// --------------------------------------------
	//
	// * **Class:** HaFormValidation
	// * **Author:** Cory Shaw
	//
	// Highlights and focuses errous form fields on submit

	'use strict';

	var module = angular.module('haFormValidationModule', []);

	module.directive('haFormValidation', ['$timeout', function ($timeout) {

		var HaFormValidationController = function (/* $scope */) {

		};

		HaFormValidationController.$inject = ['$scope'];

		var HaFormValidationLink = function ($scope, elem) {

			$timeout(function () {
				$scope.theFormName = elem.attr('name');
				$scope.theForm = $scope[$scope.theFormName];
				$scope.$emit('$haFormValidationReady', $scope.theForm);
				$scope.theForm.validate = $scope.fireValidation;
			}, 0);

			$scope.fireValidation = function (e) {
				$scope.$broadcast('validateForm');

				if ($scope.theForm.$valid) {
					$scope.$emit('haFormValidationSuccess', {
						'formName': $scope.theFormName,
						'formScope': $scope.theForm,
						'event': e
					});
				} else {
					var firstInValidElement = $('input.ng-invalid:first');
					var haModalParent = null;
					if ($('form[name=\'' + $scope.theFormName + '\']').get(0)) {
						firstInValidElement = $('form[name=\'' + $scope.theFormName + '\']').find('input.ng-invalid:first');
						haModalParent = $('form[name=\'' + $scope.theFormName + '\']').parents().find('.ha-modal');
					}

					if (haModalParent != null && haModalParent.get(0)) {
						var childPosTop = $(firstInValidElement).offset().top;
						var parentPosTop = $(haModalParent).offset().top;
						$(haModalParent).animate({scrollTop: (parentPosTop - childPosTop)}, 'slow');
					}
					else {
						$('body, html').animate({scrollTop: $('.ng-invalid:not(form.ng-invalid):first').offset().top - 140}, 'slow');
					}

					$timeout(function () {
						$(firstInValidElement).focus();
					}, 300);

					if (e && e.preventDefault) {
						e.preventDefault();
					}
				}
			};

			$scope.safeApply = function (fn) {
				var phase = this.$root.$$phase;
				if (phase === '$apply' || phase === '$digest') {
					if (fn && (typeof (fn) === 'function')) {
						fn();
					}
				} else {
					this.$apply(fn);
				}
			};
			//Added for pristine
			elem.on('reset', function () {
				$scope.$broadcast('pristine');
				$scope.theForm.$setPristine();
			});

			elem.on('submit', $scope.fireValidation);
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaFormValidationLink,
			controller: HaFormValidationController
		};
	}]);
})(angular);
;
(function (angular) {

	'use strict';

	// Ha Password Strength Directive
	// --------------------------------------------
	//
	// * **Class:** HaPasswordStrength
	// * **Author:** Cory Shaw, Nathan Probst & Jamie Perkins
	//
	// When applied to input fields, typing in the field will reveal a password strength indicator

	// Passwords requirements:
	//• Must be 10 – 16 characters in length
	//• Must have at least 1 lower case letter
	//• Must have at least 1 upper case letter
	//• Must have at least 2 letters  (sort of repetitive if we are already requiring the two above)
	//• Must have at least 1 number
	//• Can’t be the same as the email (anything before the @ symbol – so can’t be “asdf123” if email is asdf123@gmail.com)
	//• Can’t contain HM Number

	//
	// In-line Password Strength Check will display password strength as user types, including the following states/rules:
	// 1. Too Short
	// 2. Weak: 10 characters or more, but either all numeric or all alpha, or derivative of Username*
	// 4. Good: 10 characters or more, combination of alpha and numeric, and NOT derivative of Username*
	// 5. Strong: 10 characters or more, combination of alpha and numeric, and NOT derivative of Username*, special characters/punctuation
	//
	// Only Good and Strong Passwords will be accepted.
	//
	// * "derivative of Username" is defined as (Levenshtein Distance < 5).


	// SEE: http://en.wikipedia.org/wiki/Levenshtein_distance
	// SEE: http://andrew.hedges.name/experiments/levenshtein
	var levenshtein = (function () {
		function min(x, y, z) {
			if (x < y && x < z) {
				return x;
			}
			if (y < x && y < z) {
				return y;
			}
			return z;
		}

		return function (a, b) {
			// Degenerate cases
			if (a === b) {
				return 0;
			}
			if (a.Length === 0) {
				return b.length;
			}
			if (b.Length === 0) {
				return a.length;
			}

			if (a.indexOf(b) > -1) {
				return 0;
			}

			var cost;
			var m = a.length;
			var n = b.length;

			if (m < n) {
				var swap;
				swap = a;
				a = b;
				b = swap;
				swap = m;
				m = n;
				n = swap;
			}

			var r = [];
			r[0] = [];
			for (var c = 0; c < n + 1; ++c) {
				r[0][c] = c;
			}

			for (var i = 1; i < m + 1; ++i) {
				r[i] = [];
				r[i][0] = i;
				for (var j = 1; j < n + 1; ++j) {
					cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1;
					r[i][j] = min(r[i - 1][j] + 1, r[i][j - 1] + 1, r[i - 1][j - 1] + cost);
				}
			}

			return r[r.length - 1][r[r.length - 1].length - 1];
		};
	})();

	var module = angular.module('haPasswordStrengthModule', []);

	module.directive('haPasswordStrength', ['$log', 'haRegexService', function ($log, $regex) {

		var HaPasswordStrengthController = ['$scope', function ($scope) {
			this.user = undefined;
			this.email = undefined;
			this.hmno = undefined;
			this.pass = undefined;

			$scope.$parent.pwStrength = 'NONE';

			this.test = function (ngModelCtrl) {

				ngModelCtrl.$setValidity('tooShort', true);
				ngModelCtrl.$setValidity('tooLong', true);
				ngModelCtrl.$setValidity('pattern', true);
				ngModelCtrl.$setValidity('alikeness', true);

				// $log.debug('username: ', this.user);
				// $log.debug('email: ', this.email);
				// $log.debug('hmno: ', this.hmno);

				// TOO SHORT
				if (this.pass && this.pass.length < 10) {
					ngModelCtrl.$setValidity('tooShort', false);
					$scope.$parent.pwStrength = 'SHORT';
					//$log.debug('password: '+this.pass+' - too short');
					return;
				}

				// TOO LONG
				if (this.pass && this.pass.length > 16) {
					ngModelCtrl.$setValidity('tooLong', false);
					$scope.$parent.pwStrength = 'LONG';
					//$log.debug('password: '+this.pass+' - too long');
					return;
				}

				// LIKE USERNAME / EMAIL / HM NUMBER
				var MIN_LD = 5;
				if (this.pass && this.pass.length && (this.user || this.email || this.hmno)) {
					var alikeUsername;
					var alikeEmail;
					var alikeHMNO;
					// Case-insensitive distance
					if (this.user) {
						var levenshteinUsernameDistance = levenshtein(this.pass.toUpperCase(), this.user.toUpperCase());
						alikeUsername = levenshteinUsernameDistance < MIN_LD;
					}
					if (this.email) {
						var levenshteinEmailDistance = levenshtein(this.pass.toUpperCase(), this.email.toUpperCase());
						alikeEmail = levenshteinEmailDistance < MIN_LD;
					}
					if (this.hmno) {
						var levenshteinHmnoDistance = levenshtein(this.pass.toUpperCase(), this.hmno);
						alikeHMNO = levenshteinHmnoDistance < MIN_LD;
					}

					var alikeness = (alikeUsername || alikeEmail || alikeHMNO);

					ngModelCtrl.$setValidity('alikeness', !alikeness);
					if (alikeness) {
						$scope.$parent.pwStrength = 'ALIKE';
						//$log.debug('password: '+this.pass+' - too much alikeness');
						return;
					}
				}

				// WEAK
				var ok = false;
				var pwStrength = 'NONE';
				if (this.pass && this.pass.length) {
					pwStrength = 'WEAK';
					ngModelCtrl.$setValidity('pattern', false);

					// GOOD (passes)
					if ($regex.password.test(this.pass)) {
						ngModelCtrl.$setValidity('pattern', true);
						pwStrength = 'GOOD';
						ok = true;

						// STRONG (contains special characters)
						if ((/[^A-Za-z0-9]+/).test(this.pass)) {
							pwStrength = 'STRONG';
						}

					}
				}
				$scope.$parent.pwStrength = pwStrength;
				//$log.debug('password: '+this.pass+' strength: '+pwStrength);
				return ok;

			};
		}];

		var HaPasswordStrengthLink = function ($scope, $el, $attrs, ctrls) {
			var ctrl = ctrls[0];
			var ngModelCtrl = ctrls[1];

			// watch own value and re-validate on change
			ngModelCtrl.$parsers.unshift(function (value) {
				ctrl.pass = value;
				ctrl.test(ngModelCtrl);
				return value;   // pass-through
			});

			// watch username value and re-validate on change
			$scope.$watch($attrs.usernameModel, function (value) {
				if (value && value.length) {
					ctrl.user = value;
					ctrl.test(ngModelCtrl);
				}
			});
			// watch email value and re-validate on change
			$scope.$watch($attrs.emailModel, function (value) {
				// only care about username portion
				if (value && value.length) {
					ctrl.email = value.substr(0, value.indexOf('@'));
					ctrl.test(ngModelCtrl);
				}
			});

			// watch hmno value and re-validate on change
			$scope.$watch($attrs.hmnoModel, function (value) {
				if (value && value.toString().length) {
					ctrl.hmno = value.toString();
					ctrl.test(ngModelCtrl);
				}
			});
		};

		return {
			restrict: 'A',
			require: ['haPasswordStrength', 'ngModel'],
			link: HaPasswordStrengthLink,
			controller: HaPasswordStrengthController,
			transclude: true,
			templateUrl: ''
		};
	}]);

	module.directive('haPasswordMatch', [function () {
		return {
			require: 'ngModel',
			link: function ($scope, $el, $attrs, ctrl) {
				var p1;
				var p2;

				function test() {
					if (p1 != null && p2 != null) {
						ctrl.$setValidity('passwordMatch', (p1 === p2));
					}
				}

				// watch own value and re-validate on change
				ctrl.$parsers.unshift(function (value) {
					p1 = value;
					test();
					return value;   // pass-through
				});

				// watch password value and re-validate on change
				$scope.$watch($attrs.haPasswordMatch, function (value) {
					p2 = value;
					test();
				});
			}
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Window Events Directive
	// --------------------------------------------
	//
	// * **Class:** HaWindowEvents
	// * **Author:** George Pantazis
	//
	// Broadcaster for global window/body events.

	'use strict';

	var module = angular.module('haWindowEventsModule', []);

	module.directive('body', function () {

		var HaWindowEventsLink = function ($scope, $el) {

			$el.on('click', function (e) {
				$scope.$root.$emit('HaWindow:clicked', e);
			});

			$scope.$emit('$haWindowEventsReady');
		};

		return {
			restrict: 'E',
			scope: false,
			link: HaWindowEventsLink
		};
	});

})(angular);
;
(function (angular) {

	// No Click Focus Directive
	// --------------------------------------------
	//
	// * **Class:** NoClickFocus
	// * **Author:** George Pantazis
	//
	// For anchors that serve as navigation, yet have focus states for accessibility, don't focus on click.

	'use strict';

	var module = angular.module('noClickFocusModule', []);

	module.directive('noClickFocus', function () {

		var NoClickFocusLink = function ($scope, $el) {
			$el.click(function () {
				$el.blur();
			});
		};

		return {
			restrict: 'A',
			scope: false,
			link: NoClickFocusLink
		};
	});

})(angular);
;
(function (angular) {

	// Ha Toggle Directive
	// --------------------------------------------
	//
	// * **Class:** HaToggle
	// * **Author:** Jamie Perkins
	//
	// UI element that represents a boolean state

	'use strict';

	var module = angular.module('haToggleModule', []);

	module.directive('haToggle', ['haConfig', function (haConfig) {
		return {
			restrict: 'A',
			scope: {
				'ngModel': '=',
				'onLabel': '=',
				'offLabel': '='
			},
			templateUrl: haConfig.getTemplateUrl('ha-toggle-base-template.html'),
			transclude: true,
			link: function ($scope, $el) {

				// console.log('ha-toggle model: ');
				// console.log($scope.ngModel);

				$el.find('input').prop('checked', $scope.ngModel);

				$scope.toggle = function () {
					$scope.ngModel = !$scope.ngModel;
					$el.find('input').prop('checked', $scope.ngModel);
				};

			}
		};
	}]);

})(angular);;
(function (angular) {

	// Ha Keyboard Directive
	// --------------------------------------------
	//
	// * **Class:** HaKeyboard
	// * **Author:** George Pantazis
	//
	// A collection of directives that abstract keyboard interactions.

	'use strict';

	var module = angular.module('haKeyboardModule', []);

	module.directive('haKeyEnter', function () {
		return function (scope, element, attrs) {
			element.bind('keydown keypress', function (event) {
				if (event.which === 13) {
					scope.$apply(function () {
						scope.$eval(attrs.haKeyEnter);
					});
					event.preventDefault();
				}
			});
		};
	});

	module.directive('haKeyUp', function () {
		return function (scope, element, attrs) {
			element.bind('keydown keypress', function (event) {
				if (event.which === 38) {
					scope.$apply(function () {
						scope.$eval(attrs.haKeyUp);
					});
					event.preventDefault();
				}
			});
		};
	});

	module.directive('haKeyDown', function () {
		return function (scope, element, attrs) {
			element.bind('keydown keypress', function (event) {
				if (event.which === 40) {
					scope.$apply(function () {
						scope.$eval(attrs.haKeyDown);
					});
					event.preventDefault();
				}
			});
		};
	});

})(angular);
;
(function (angular) {
	'use strict';
	var module = angular.module('haErrorsModule', []);
	var directive = ['haSitecoreStrings', '$timeout', function ($scs, $timeout) {

		var defaults = {
			editable: $scs.get('Forms.patternError').then(function (value) {
				return (defaults.editable = value);
			}),
			required: $scs.get('Forms.requiredError').then(function (value) {
				return (defaults.required = value);
			}),
			language: $scs.get('Forms.languageError').then(function (value) {
				return (defaults.language = value);
			}),
			pattern: $scs.get('Forms.patternError').then(function (value) {
				return (defaults.pattern = value);
			}),
			date: $scs.get('Forms.dateError').then(function (value) {
				return (defaults.date = value);
			}),
			parse: ''
		};

		return {
			require: ['?^form', '?ngModel', '?^haInput'],
			restrict: 'E',
			link: function ($scope, $el, attrs, requirements) {
				var name = $el[0].name;
				var form = requirements[0];
				var model = requirements[1];
				var haInput = requirements[2];
				var allowNonEnglish = typeof attrs.allowNonEnglish !== 'undefined';

				if ((!form || !model || haInput || !$el.is('[ha-errors]'))) {

					if (allowNonEnglish || !$el.is(':not([ha-errors]):not(select):not(:checkbox):not(:radio):not(:hidden):not(:submit):not(:button)')) {
						return;
					}


					$el.on('keydown input', function (e) {
						if (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) {
							return true;
						}

						var val = $(this).val();
						var isValid = !/[^\u0000-\u007F]+/.test(val);

						if (e.type === 'input' && !isValid) {
							$el.val(val.replace(/[^\u0000-\u007F]+/g, ''));

							return true;
						}

						return isValid;
					});

					return;
				}

				if (typeof form.attempts === 'undefined') {
					form.attempts = 0;
					$($el[0].form).on('submit', validate);
				}

				var errors = model.$error;
				var messages = angular.extend({}, defaults, $scope.$eval(attrs.haErrors || '{}'));

				if (angular.version.major === 1 && angular.version.minor < 7) {
					$el.one('blur', function () {
						$scope.$evalAsync(function () {
							model.$untouched = !(model.$touched = true);
							$el.removeClass('ng-untouched').addClass('ng-touched');
						});
					});
					$el.addClass('ng-untouched');
					model.$untouched = !(model.$touched = false);
					form.$validate = validate.bind($el[0].form);
				}

				//allow the error <em>'s to be manually specified but wait for interpolation
				var $em;
				var $comment;
				$timeout(function () {

					$em = $('em[for="' + name + '"]');
					if (!$em.length) {
						$em = $('<em>').attr('for', name);
						if (!$el.is('[required]:radio,[required]:checkbox')) {
							$em.insertAfter($el);
						} else {
							$el.closest(':not(ul):not(ol):not(li):not(input)').append($em);
						}
					}

					$comment = $(document.createComment('ha-errors for=' + name)).insertBefore($em);

					// Watch text inputs and textareas
					if (!allowNonEnglish && $el.is(':not(select):not(:checkbox):not(:radio):not(:hidden):not(:submit):not(:button)')) {
						$scope.$watch(function () {
							return $el.val();
						}, function (v) {
							if (v) {
								model.$setValidity('language', !/[^\u0000-\u007F]+/.test(v));
							}
						});
					}

					$scope.$watch(prop, function (error) {
						$el.attr('aria-invalid', model.$invalid);
						$em.text('').detach();
						if (error) {
							$em.html(getMessage(error)).insertAfter($comment);
						}
					});
				});

				function validate(externalform) {

                    // for cases where submit button might live outside the form (meaning form would be otherwise undefined), allow substitution of a passed-in form instead
                    if (!form) {
                        form = externalform;
                    }

					form.attempts++;
					form.$submitted = true;

                    var formelement = $('[name="' + form.$name + '"]');

					formelement.addClass('submitted');

					$timeout(function () {
						//use timeout to force the watches to re-evaluate so error messages render
					}, 0);

					if (form.$invalid) {
						var $firstError = $('.ng-invalid:not(div):first', formelement);
						var $modal = $firstError.closest('.modalContainer');
						var $scrollEl = $modal.size() ? $modal : $('html,body')
						$scrollEl.animate({scrollTop: $firstError.offset().top - 50}, function() {
							$firstError.focus().select();
						});
					}

					return form.$valid;
				}

				function prop() {

					if (!(form.$submitted || model.$touched)) {
						return false;
					}

					if (errors.required) {
						return 'required';
					}
					if (errors.language) {
						return 'language';
					}
					if (errors.pattern) {
						return 'pattern';
					}

					for (var n in errors) {
						if (errors.hasOwnProperty(n) && errors[n]) {
							return n;
						}
					}
				}

				function getMessage(error) {
					var message = messages[error];
					if (typeof message === 'undefined') {
						console.error('Missing "' + error + '" message for input:', $el[0]);
						return 'Unknown Error'; //only happens when a dev forgot something!
					}
					if (message.then) {
						message.then(function (value) {
							$em.html(messages[error] = value);
						});
						return '...'; //what else can we do until the promise resolves?
					}

					return messages[error];
				}
			}
		};
	}
	];

	module.directive('input', directive);
	module.directive('textarea', directive);
	module.directive('select', directive);

})(angular);
;
(function (angular) {

	// Ha Foot Note Directive
	// --------------------------------------------
	//
	// * **Class:** HaFootNote
	// * **Author:** George Pantazis
	//
	// Behavior for transposing footnotes to a data-model that can be presented
	// elsewhere in the page, primarily in HaGlobalFooter.

	'use strict';

	var module = angular.module('haFootNoteModule', []);
	var currentStatic = 0;
	var currentNumeric = 1;

	module.directive('haFootNote', ['haConfig', function (haConfig) {

		var HaFootNoteController = function ($scope, $rootScope) {

			$rootScope.footnotes = $rootScope.footnotes || {};

			$scope.addFootnote = function (text) {

				$rootScope.footnotes[$scope.type] = $rootScope.footnotes[$scope.type] || [];

				$rootScope.footnotes[$scope.type].push({
					'id': $scope.id,
					'label': $scope.label,
					'text': text
				});
			};

		};

		HaFootNoteController.$inject = ['$scope', '$rootScope'];

		var HaFootNoteLink = function ($scope, $el, $attrs) {

			$scope.setValues = function () {
				if (typeof $attrs.labelNumber === 'string') {
					$scope.type = 'numeric';
					$scope.id = currentNumeric++;
				} else {
					$scope.type = 'static';
					$scope.label = $attrs.label;
					$scope.id = String.fromCharCode(65 + (currentStatic++));
				}
			};

			$scope.setValues();
			if ($scope.DisclaimerFootNote !== '') {
				$scope.addFootnote($scope.DisclaimerFootNote);
			}
			else {
				$scope.addFootnote($el.find('[ng-transclude]').text());
			}
		};

		return {
			restrict: 'A',
			transclude: true,
			scope: true,
			link: HaFootNoteLink,
			templateUrl: haConfig.getTemplateUrl('ha-foot-note-base-template.html'),
			controller: HaFootNoteController
		};
	}]);

})(angular);
;
(function (angular) {

	// Select On Click Directive
	// --------------------------------------------
	//
	// * **Class:** SelectOnClick
	// * **Author:** Cory Shaw
	//
	// Auto selects an input field on click

	'use strict';

	var module = angular.module('selectOnClickModule', []);

	module.directive('selectOnClick', function () {
		return function (scope, element) {
			element.bind('click', function () {
				this.select();
			});
		};
	});

})(angular);
;
(function (angular) {

	// Ha Reveal On Load Directive
	// --------------------------------------------
	//
	// * **Class:** HaRevealOnLoad
	// * **Author:** George Pantazis
	//
	// Reveals elements when Angular has loaded.

	'use strict';

	var module = angular.module('haRevealOnLoadModule', []);

	module.directive('haRevealOnLoad', function () {

		return {
			restrict: 'A',
			scope: false,
			link: function ($scope, el) {
				setTimeout(function () {
					el.addClass('ha-reveal-on-load-active');
					$scope.contentLoaded = true;
				}, 0);
			}
		};
	});

})(angular);
;

(function (angular) {
	'use strict';

	var module = angular.module('haWhenReadyModule', []);
	module.directive('haWhenReady', [function () {
		return {
			priority: Number.MIN_SAFE_INTEGER, // execute last, after all other directives if any.
			restrict: "A",
			link: function ($scope, $element, $attributes) {
				document.lazyLoadInstance.update(); 
			}
		};
	}]);
})(angular);
;
(function (angular) {

	// Hawaiian Draggable Directive
	// ============================
	//
	// * **Class:** HaDraggable
	// * **Author:** George Pantazis
	//
	// Wrap jQuery UI's draggable functionality with an angular directive.

	'use strict';

	var module = angular.module('haDraggableModule', []);

	module.directive('haDraggableContainer', function () {

		var HaDraggableContainerLink = function ($scope, $el) {

			$scope.exampleMethod = function () {
				return $el.scope();
			};

			$scope.$emit('$methodsBound');
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaDraggableContainerLink
		};
	});

	module.directive('haDraggable', function () {

		var HaDraggableLink = function ($scope, $el) {

			$el.draggable({
				start: function (e) {
					$scope.$emit('$dragStart', e);
				},
				drag: function (e) {
					$scope.$emit('$dragging', e);
				},
				stop: function (e, ui) {
					$scope.$emit('$dragEnd', e, ui);
				}
			});

			$scope.updateDraggableSettings = function (settings) {
				$el.draggable(settings);

				return $el.scope();
			};

		};

		return {
			restrict: 'A',
			scope: true,
			priority: 10,
			link: HaDraggableLink
		};
	});

})(angular);
;
(function (angular) {

	// Hawaiian Scroll Directive
	// =======================
	//
	// * **Class:** haScrollTo
	// * **Author:** Nathan Probst
	//
	// Smoothly scroll to an anchor. Uses angular-scroll.js
	// SEE: https://github.com/durated/angular-scroll/

	'use strict';

	angular.module('haScrollModule', ['duScroll'])

	.directive('haScrollTo', ['duSmoothScrollDirective', function (duSmoothScroll) {

		var HaScrollLink = function ($scope, $element, $attrs) {

			var hrefTarget = '#' + $attrs.haScrollTo;
			var offset = 90;    // Acommodate sticky header by default

			if ($attrs.offset != null) {
				offset = $attrs.offset;
			}

			$attrs.$set('href', hrefTarget);
			$attrs.$set('offset', offset);
			duSmoothScroll[0].link($scope, $element, $attrs);
		};

		return {
			restrict: 'A',
			priority: -1,
			link: HaScrollLink
		};
	}]);

})(angular);
;
(function (angular) {

	// Hawaiian Data Directive
	// =======================
	//
	// * **Class:** HaData
	// * **Author:** George Pantazis
	//
	// Binds string of data, or JSON object, to Angular object's scope.

	'use strict';

	var module = angular.module('haDataModule', ['haDataCacheService']);

	var haDataCacheFactory = function (haDataCacheService) {

		var HaDataLink = function ($scope, $el, $attrs) {

			$scope.haDataHref = $attrs.haData || $attrs.haDataCache;

			haDataCacheService.get($scope.haDataHref)
			.success(function (data) {
				$.extend($scope, data);
				$scope.$emit('$haDataLoaded');
			});
		};

		return {
			restrict: 'A',
			scope: true,
			priority: -1,
			link: HaDataLink
			//controller: HaDataController
		};
	};

	module.directive('haData', ['haDataCacheService', haDataCacheFactory]);
	module.directive('haDataCache', ['haDataCacheService', haDataCacheFactory]);

})(angular);
;
(function (angular) {

	// Hawaiian Tail Directive
	// =======================
	//
	// * **Class:** HaTail
	// * **Author:** Nathan Probst
	//
	// Auto-scoll to end of div. Think "tail -f".

	'use strict';

	var mod = angular.module('haTailModule', ['ng']);

	mod.directive('haTail', ['$parse', '$interval', function ($parse, $interval) {

		var HaTailLink = function ($scope, $el, $attrs) {
			var tailMode = $parse($attrs.haTail);

			function tail(element) {
				if (!tailMode($scope)) {
					return;
				}

				var el = $(element).get(0);
				if (el != null) {
					el.scrollTop = el.scrollHeight;
					// el.animate({
					//   scrollTop: el.scrollHeight
					// }, 500);
				}
			}

			if ($attrs.ngModel != null) {
				$scope.$watch($attrs.ngModel, function () {
					tail($el);
				});
			} else {
				var interval = $interval(function () {
					tail($el);
				}, 500, false);

				$scope.$on('$destroy', function () {
					$interval.cancel(interval);
				});
			}
		};

		return {
			restrict: 'A',
			priority: -1,
			link: HaTailLink
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha JSON Directive
	// --------------------------------------------
	//
	// * **Class:** HaJson
	// * **Author:** Nathan Probst
	//
	// A filter to display more than the default filter.json filter.

	'use strict';

	var mod = angular.module('haJson', []);

	// NEP: Don't hide the '$' members...but basicaly this is filter.json
	mod.filter('haJson', function () {
		function isWindow(obj) {
			return obj && obj.document && obj.location && obj.alert && obj.setInterval;
		}

		function isScope(obj) {
			return obj && obj.$evalAsync && obj.$watch;
		}

		function toJsonReplacer(key, value) {
			var val = value;
			if (isWindow(value)) {
				val = '$WINDOW';
			} else if (value && document === value) {
				val = '$DOCUMENT';
			} else if (isScope(value)) {
				val = '$SCOPE';
			}
			return val;
		}

		return function (object) {
			if (typeof object === 'undefined') {
				return undefined;
			}
			return JSON.stringify(object, toJsonReplacer, '  ');
		};
	});

})(angular);
;
(function (angular) {

	// HA Name
	// --------------------------------------------
	//
	// * **Class:** haInputName
	// * **Author:** Nathan Probst
	//
	// A directive to allow interpolation of name attributes onto form inputs

	'use strict';

	var module = angular.module('haInputName', []);

	module.directive('haInputName', [
		'$interpolate',
		function ($interpolate) {
			return {
				restrict: 'A',
				priority: 1000,
				scope: false,
				link: function ($scope, $el) {
					var name = $interpolate($el.attr('ha-input-name'), false, null, true)($scope);
					$el.removeAttr('ha-input-name');
					var input = $el.find('input');
					input.attr('name', name);
				}
			};
		}
	]);

})(angular);
;
(function (angular) {

	// Ha Alert Directive
	// --------------------------------------------
	//
	// * **Class:** haWatchTrailer
	// * **Author:** Cory Shaw
	//
	// Shows a modal with a youtube video

	'use strict';

	var module = angular.module('haWatchTrailerModule', []);

	module.directive('haWatchTrailer', ['$window', 'haModal', 'haConfig', function ($window, haModal, haConfig) {

		var HaWatchTrailerController = function ($scope) {


			var trailerTemplateUrl = haConfig.getTemplateUrl('ha-watch-trailer-modal-template.html');
			if ($scope.$root.isMobile) {
				trailerTemplateUrl = haConfig.getTemplateUrl('ha-watch-trailer-modal-mobile-template.html');
			}

			$scope.showTrailer = function (youtubeID) {
				$scope.youtubeURL = '//www.youtube.com/embed/' + youtubeID + '?autoplay=1&rel=0&origin=http://hawaiianairliens.comyoutubeID';
				haModal(trailerTemplateUrl, {
					backdrop: 'true',
					id: 'watch-video',
					scope: $scope
				});
			};
		};

		HaWatchTrailerController.$inject = ['$scope'];

		return {
			restrict: 'A',
			scope: true,
			controller: HaWatchTrailerController
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Loading Spinner Directive
	// --------------------------------------------
	//
	// * **Class:** HaLoadingSpinner
	// * **Author:** Mari Yamaha
	//
	// Injects Loading Spinner html and styling where used

	'use strict';

	var module = angular.module('haLoadingSpinnerModule', []);

	module.directive('haLoadingSpinner', function () {

		var HaLoadingSpinnerLink = function ($scope, $el, $attrs) {

			if ($attrs.small !== undefined) {
				$el.children('.ha-loading-spinner').addClass('small');
			}
			if ($attrs.white !== undefined) {
				$el.children('.ha-loading-spinner').addClass('white');
			}
			$scope.loadingText = '';
			if ($attrs.loadingText !== undefined) {
				$scope.loadingText = $attrs.loadingText;
			}

			$scope.$emit('$haLoadingSpinnerReady');
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaLoadingSpinnerLink,
			transclude: true,
			template: '<div class="ha-loading-spinner" tabindex="0"><span class="sr-only">{{loadingText}}</span><div class="spin"><div></div></div></div>'
		};
	});

	module.directive('haLoadingSpinnerWithText', function () {

		var HaLoadingSpinnerWithTextLink = function ($scope) {

			$scope.$emit('$haLoadingSpinnerWithTextReady');
		};

		return {
			restrict: 'A',
			scope: true,
			link: HaLoadingSpinnerWithTextLink,
			transclude: true,
			template: '<div class="ha-loading-spinner-with-text" ha-reveal-on-load tabindex="0"><div class="ha-loading-spinner small"><div class="spin"><div></div></div></div><div class="ha-loading-text"><span ng-transclude></span></div></div>'
		};
	});

})(angular);

;
(function (angular) {

	// Focus on Display Directive
	// --------------------------------------------
	//
	// * **Class:** FocusOnDisplay
	// * **Author:** Michael Toymil
	//
	// Focus's on a given element when it is shown

	'use strict';

	var module = angular.module('focusOnDisplayModule', []);

	module.directive('focusOnDisplay', function () {
		return {
			restrict: 'A',
			link: function ($scope, element, attrs) {

				// skip this behavior for IE9, bc it suppresses placeholder shim
				if (!$('html').hasClass('lte-ie9')) {
					attrs.$observe('focusOnDisplay', function () {
						element.focus();
					});
				}
			}
		};
	});

})(angular);
;
(function (angular) {

	/*
	 for adding behavior to any standard html element
	 */

	'use strict';

	var module = angular.module('haElementDirectivesModule', []);

	// for fixing IE9 select bug, where only first letter of option is displayed
	// using ng-options in combination with this should fix all occurrences.
	module.directive('select', ['$interval', '$timeout', function ($interval, $timeout) {
		return {
			restrict: 'E',
			link: function ($scope, $el) {

				if ($('html').hasClass('lte-ie9')) {

					var waitForOptions;
					if ($el.children('option').length <= 1) {
						// wait for options to populate
						var count = 0;
						waitForOptions = $interval(function () {
							count++;
							if ($el.children('option').length > 1) {
								$interval.cancel(waitForOptions);
								$el.width($el.css('width'));
							}
							if (count > 40) {
								$interval.cancel(waitForOptions);
							}
						}, 50);
					} else {
						$timeout(function () {
							$el.width($el.css('width'));
						}, 50);
					}
				}
			}
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Button Spinner
	// --------------------------------------------
	//
	// * **Class:** HaButtonSpinner
	// * **Author:** Jamie Perkins
	//
	// Creates a spinner inside a button when it is clicked

	'use strict';

	var module = angular.module('haButtonSpinnerModule', []);

	module.directive('haButtonSpinner', function () {

		return {
			template: '<span class="button-spinner"><span><span></span></span></span><span ng-transclude></span>',
			transclude: true,
			restrict: 'A',
			scope: {
				showSpinner: '='
			},
			link: function ($scope, $el) {

				$scope.$watch(function (scope) {
					return scope.showSpinner;
				}, function (newValue) {
					$el.blur();
					if (newValue) {
						$el.children('.button-spinner').css({opacity: 1});
						$el.css({paddingLeft: 60});
						$el.prop('disabled', true);
					} else {
						$el.children('.button-spinner').css({opacity: 0});
						$el.css({paddingLeft: 30});
						$el.prop('disabled', false);
					}
				});
			}
		};

	});

})(angular);
;
(function (angular) {

	// Ha Disable on Click
	// --------------------------------------------
	//
	// * **Class:** haDisableOnClick
	// * **Author:** Mark Hagelberg
	//
	// Disable anchor tag (link) after first click, and add a spinner to the right.
	// Useful for eliminating repeated IIS requests (multi-clicks) when page response is slow.
	// To be used only on <a href="/url"> elements with a button-like UI.

	'use strict';

	var module = angular.module('haDisableOnClickModule', []);

	module.directive('haDisableOnClick', ['$timeout', '$window', function ($timeout, $window) {
		return {
			restrict: 'A',
			link: linkFn,
			scope: true,
			template: '<a ng-mouseup="disableHref()" ng-transclude></a>',
			transclude: true,
			replace: true
		};

		function linkFn(scope, element, attrs) {
			var whiteOrDefault = attrs.white !== undefined ? 'white ' : '';
			var spinner = '<div class="ha-loading-spinner ' + whiteOrDefault + 'small"><div><div></div></div></div>';

			scope.disableHref = function () {
				if (!scope.hrefIsDisabled && !!attrs['href']) {
					var hrefValue = attrs.href;
					scope.hrefIsDisabled = true;
					element.append(spinner).focus().attr('href', '#');

					$timeout(function () {
						$window.location = hrefValue;

						$timeout(function () {
							// restore href if user has waited a while
							element.attr('href', hrefValue);
							scope.hrefIsDisabled = false;
						}, 60000);
					}, 300);
				}	
			};

			element.on('$destroy', function () {
				scope.hrefIsDisabled = false;
			});
		};

	}]);

})(angular);
;
(function (angular) {

	//
	// --------------------------------------------
	//
	// * **Class:** Launchdarkly Feature Flags
	// * **Author:** Francis Oyeniyi
	//
	// Get flags from  LaunchDarkly or C# session if already retrived. For pages without C# view Model object.
	// -Parameters-
	// Key:		Config value key for featureFlag key
	// type:	Type of Feature flag variant
	// variant:	value to assert for feature to display
	// useCache(optional): Do not unclude to prevent posibility of Evaluation commin from session.
	//
	// Example: {key:'ABGPhase2Features', type:'boolean',variant:true,useCache:true}
	// WARNING! Could create false positive result if variant matches HA.FeatureFlag DLL default values.
	//Make sure correct Feature Key is in Web.Config.
	'use strict';

	var module = angular.module('haLaunchDarklyModule');


	module.directive('launchDarklyFeature', ["haLaunchDarklyService", function (haLaunchDarklyService) {

		return {
			restrict: 'A',
			controller: ['$scope', function($scope) {
				$scope.launchDarklyFlagReady = false;
			}],
			scope: true,
			link: function ($scope, element, attrs) {
				var attributes = $scope.$eval(attrs.launchDarklyFeature);
				var cacheEvaluation = attributes.useCache == undefined || attributes.useCache !== true ? false : true;
				element.addClass("hidden");
				haLaunchDarklyService.GetFlagVariant(attributes.key, attributes.type, attributes.variant, cacheEvaluation).then(
					function (response) {
						if (response.data === "DISABLED") {
							element.remove();
						}
						if (response.data === "ENABLED") {
							$scope.launchDarklyFlagReady = true;
							element.removeClass("hidden");
						}
					}
				, function(error) {
					console.error("Launch Darkly Error: "+error+"	KEY: "+attributes.key);
					element.remove();
				});
			}
		};
	}]);

})(angular);
;
(function (angular) {

	// Timed Hide
	// --------------------------------------------
	//
	// * **Class:** haTimedHide
	// * **Author:** Michael Toymil
	//
	// Prevent the display of the directive element if a cookie is set.

	'use strict';

	var module = angular.module('haTimedHideModule', ['ngCookies']);
	var COOKIE_BASE = 'HA_TIMED_HIDE_';
	module.directive('haTimedHide', ['$cookies', function ($cookies) {
		return {
			restrict: 'A',
			scope: true,
			link: function ($scope, element, attrs) {
				var minutes = parseInt(attrs.hideMinutes);
				minutes = isNaN(minutes) ? 0 : minutes;
				var id = attrs.hideId;
				if (!id) {
					element.addClass('shown');
					console.log("No id for ha-timed-hide");
					return;
				}
				var cookieName = COOKIE_BASE + id;
				if (!$cookies.get(cookieName)) {
					// No hide cookie found, show the element
					element.addClass('shown');
				} else if ($cookies.get(cookieName) != minutes) {
					// The expiration value changed, bust the old cookie.
					$cookies.remove(cookieName);
					element.addClass('shown');
				}

				$scope.timedClose = function() {
					element.removeClass('shown');
					if (minutes && id) {
						var d = new Date();
						d.setMinutes(d.getMinutes() + minutes)
						$cookies.put(cookieName, minutes, {expires: d, path: '/'});
					}
				}
			}
		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	var module = angular.module('haResponsiveAttributeModule', []);

	module.directive('haResponsiveAttribute', ['$window', '$parse', function ($window, $parse) {

		return {
			restrict: 'A',
			link: function ($scope, $el, $attrs) {
				// grab attrs
				var attributeName = $attrs.attributeName;			// string
				var baseValue = $attrs.baseValue;					// css value string (e.g '800px');
				var breakpoints = $parse($attrs.breakpoints)();     // 2d array [[(num)deviceWidthStart, (num)deviceWidthEnd, (num)scalar]]

				var calculateAttribute = function() {
					// find our device width in the breakpoint array
					var dw = $window.innerWidth;
					var scalar = 1;
					for (var i = 0; i < breakpoints.length; i++) {
						if (dw > breakpoints[i][0] && dw < breakpoints[i][1]) {
							scalar = breakpoints[i][2];
							break;
						}
					} 
					$el.css(attributeName, "calc(" + baseValue + " * " + scalar + ")");
				}
				calculateAttribute();

				// bind to resize event;
				angular.element($window).on('resize', calculateAttribute);
				$scope.$on('$destroy', function() {
					angular.element($window).off('resize', calculateAttribute);
				});
			}
		};
	}]);

})(angular);;
(function (angular) {

	// Ha Contextual Help Directive
	// --------------------------------------------
	//
	// * **Class:** HaDomModal
	// * **Author:** Jake Albaugh
	//
	// Behavior to use dom content in a modal window.

	'use strict';

	var module = angular.module('haDomModalModule', []);

	module.directive('haDomModal', ['haModal', function (haModal) {

		var HaDomModalController = function ($scope) {
			$scope.showModal = function () {
				if (!$scope.hasError && $scope.modalContent) {
					haModal($scope.modalContent, {
						id: $scope.modalId,
						backdrop: 'true'
					});
				}
			};
		};

		HaDomModalController.$inject = ['$scope', '$rootScope'];

		var HaDomModalLink = function ($scope, $el, $attrs) {
			$scope.domId = $attrs.domId;
			$scope.modalId = $attrs.modalId;
			$scope.modalContent = $attrs.domId;

			$($el).on('click', function () {
				$scope.showModal();
			});

		};

		return {
			restrict: 'A',
			scope: true,
			link: HaDomModalLink,
			controller: HaDomModalController
		};
	}]);

})(angular);
;
(function (angular) {
	'use strict';

	var module = angular.module('haDelayAutoplayModule', []);

	module.directive('haDelayAutoplay', [function () {
		return {
			link: function($scope, $el) {
				console.log('haDelayAutoplay Link');
				var el = $el[0];
				var video = $el.find('video')[0];

				// Video and support checks
				if (!video) {
					return;
				}
				if (!IntersectionObserver) {
					video.play();
				}

				// Listen for video end (not applicable when looping)
				var ended = false;
				video.addEventListener('ended', function myHandler(e) {
					ended = true;
				}, false);

				var observer = new IntersectionObserver( function(entries, observer) {
					angular.forEach(entries, function(entry) {
						if (entry.intersectionRatio > 0.5 && !ended) {
							video.play();
						}
					});
				}, {threshold: 0.5});
				observer.observe(el);
			}
		};
	}]);

})(angular);
;
(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
		typeof define === 'function' && define.amd ? define(factory) :
			(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.LazyLoad = factory());
}(this, (function () {
	'use strict';

	function _extends() {
		_extends = Object.assign || function (target) {
			for (var i = 1; i < arguments.length; i++) {
				var source = arguments[i];

				for (var key in source) {
					if (Object.prototype.hasOwnProperty.call(source, key)) {
						target[key] = source[key];
					}
				}
			}

			return target;
		};

		return _extends.apply(this, arguments);
	}

	var runningOnBrowser = typeof window !== "undefined";
	var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent);
	var supportsIntersectionObserver = runningOnBrowser && "IntersectionObserver" in window;
	var supportsClassList = runningOnBrowser && "classList" in document.createElement("p");
	var isHiDpi = runningOnBrowser && window.devicePixelRatio > 1;

	var defaultSettings = {
		elements_selector: ".lazy",
		container: isBot || runningOnBrowser ? document : null,
		threshold: 300,
		thresholds: null,
		data_src: "src",
		data_srcset: "srcset",
		data_sizes: "sizes",
		data_bg: "bg",
		data_bg_hidpi: "bg-hidpi",
		data_bg_multi: "bg-multi",
		data_bg_multi_hidpi: "bg-multi-hidpi",
		data_bg_set: "bg-set",
		data_poster: "poster",
		class_applied: "applied",
		class_loading: "loading",
		class_loaded: "loaded",
		class_error: "error",
		class_entered: "entered",
		class_exited: "exited",
		unobserve_completed: true,
		unobserve_entered: false,
		cancel_on_exit: true,
		callback_enter: null,
		callback_exit: null,
		callback_applied: null,
		callback_loading: null,
		callback_loaded: null,
		callback_error: null,
		callback_finish: null,
		callback_cancel: null,
		use_native: false,
		restore_on_error: false
	};
	var getExtendedSettings = function getExtendedSettings(customSettings) {
		return _extends({}, defaultSettings, customSettings);
	};

	/* Creates instance and notifies it through the window element */
	var createInstance = function createInstance(classObj, options) {
		var event;
		var eventString = "LazyLoad::Initialized";
		var instance = new classObj(options);

		try {
			// Works in modern browsers
			event = new CustomEvent(eventString, {
				detail: {
					instance: instance
				}
			});
		} catch (err) {
			// Works in Internet Explorer (all versions)
			event = document.createEvent("CustomEvent");
			event.initCustomEvent(eventString, false, false, {
				instance: instance
			});
		}

		window.dispatchEvent(event);
	};
	/* Auto initialization of one or more instances of lazyload, depending on the 
		options passed in (plain object or an array) */


	var autoInitialize = function autoInitialize(classObj, options) {
		if (!options) {
			return;
		}

		if (!options.length) {
			// Plain object
			createInstance(classObj, options);
		} else {
			// Array of objects
			for (var i = 0, optionsItem; optionsItem = options[i]; i += 1) {
				createInstance(classObj, optionsItem);
			}
		}
	};

	var SRC = "src";
	var SRCSET = "srcset";
	var SIZES = "sizes";
	var POSTER = "poster";
	var ORIGINALS = "llOriginalAttrs";
	var DATA = "data";

	var statusLoading = "loading";
	var statusLoaded = "loaded";
	var statusApplied = "applied";
	var statusEntered = "entered";
	var statusError = "error";
	var statusNative = "native";

	var dataPrefix = "data-";
	var statusDataName = "ll-status";
	var getData = function getData(element, attribute) {
		return element.getAttribute(dataPrefix + attribute);
	};
	var setData = function setData(element, attribute, value) {
		var attrName = dataPrefix + attribute;

		if (value === null) {
			element.removeAttribute(attrName);
			return;
		}

		element.setAttribute(attrName, value);
	};
	var getStatus = function getStatus(element) {
		return getData(element, statusDataName);
	};
	var setStatus = function setStatus(element, status) {
		return setData(element, statusDataName, status);
	};
	var resetStatus = function resetStatus(element) {
		return setStatus(element, null);
	};
	var hasEmptyStatus = function hasEmptyStatus(element) {
		return getStatus(element) === null;
	};
	var hasStatusLoading = function hasStatusLoading(element) {
		return getStatus(element) === statusLoading;
	};
	var hasStatusError = function hasStatusError(element) {
		return getStatus(element) === statusError;
	};
	var hasStatusNative = function hasStatusNative(element) {
		return getStatus(element) === statusNative;
	};
	var statusesAfterLoading = [statusLoading, statusLoaded, statusApplied, statusError];
	var hadStartedLoading = function hadStartedLoading(element) {
		return statusesAfterLoading.indexOf(getStatus(element)) >= 0;
	};

	var safeCallback = function safeCallback(callback, arg1, arg2, arg3) {
		if (!callback) {
			return;
		}

		if (arg3 !== undefined) {
			callback(arg1, arg2, arg3);
			return;
		}

		if (arg2 !== undefined) {
			callback(arg1, arg2);
			return;
		}

		callback(arg1);
	};

	var addClass = function addClass(element, className) {
		if (supportsClassList) {
			element.classList.add(className);
			return;
		}

		element.className += (element.className ? " " : "") + className;
	};
	var removeClass = function removeClass(element, className) {
		if (supportsClassList) {
			element.classList.remove(className);
			return;
		}

		element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " ").replace(/^\s+/, "").replace(/\s+$/, "");
	};

	var addTempImage = function addTempImage(element) {
		element.llTempImage = document.createElement("IMG");
	};
	var deleteTempImage = function deleteTempImage(element) {
		delete element.llTempImage;
	};
	var getTempImage = function getTempImage(element) {
		return element.llTempImage;
	};

	var unobserve = function unobserve(element, instance) {
		if (!instance) return;
		var observer = instance._observer;
		if (!observer) return;
		observer.unobserve(element);
	};
	var resetObserver = function resetObserver(observer) {
		observer.disconnect();
	};
	var unobserveEntered = function unobserveEntered(element, settings, instance) {
		if (settings.unobserve_entered) unobserve(element, instance);
	};

	var updateLoadingCount = function updateLoadingCount(instance, delta) {
		if (!instance) return;
		instance.loadingCount += delta;
	};
	var decreaseToLoadCount = function decreaseToLoadCount(instance) {
		if (!instance) return;
		instance.toLoadCount -= 1;
	};
	var setToLoadCount = function setToLoadCount(instance, value) {
		if (!instance) return;
		instance.toLoadCount = value;
	};
	var isSomethingLoading = function isSomethingLoading(instance) {
		return instance.loadingCount > 0;
	};
	var haveElementsToLoad = function haveElementsToLoad(instance) {
		return instance.toLoadCount > 0;
	};

	var getSourceTags = function getSourceTags(parentTag) {
		var sourceTags = [];

		for (var i = 0, childTag; childTag = parentTag.children[i]; i += 1) {
			if (childTag.tagName === "SOURCE") {
				sourceTags.push(childTag);
			}
		}

		return sourceTags;
	};

	var forEachPictureSource = function forEachPictureSource(element, fn) {
		var parent = element.parentNode;

		if (!parent || parent.tagName !== "PICTURE") {
			return;
		}

		var sourceTags = getSourceTags(parent);
		sourceTags.forEach(fn);
	};
	var forEachVideoSource = function forEachVideoSource(element, fn) {
		var sourceTags = getSourceTags(element);
		sourceTags.forEach(fn);
	};

	var attrsSrc = [SRC];
	var attrsSrcPoster = [SRC, POSTER];
	var attrsSrcSrcsetSizes = [SRC, SRCSET, SIZES];
	var attrsData = [DATA];
	var hasOriginalAttrs = function hasOriginalAttrs(element) {
		return !!element[ORIGINALS];
	};
	var getOriginalAttrs = function getOriginalAttrs(element) {
		return element[ORIGINALS];
	};
	var deleteOriginalAttrs = function deleteOriginalAttrs(element) {
		return delete element[ORIGINALS];
	}; // ## SAVE ##

	var setOriginalsObject = function setOriginalsObject(element, attributes) {
		if (hasOriginalAttrs(element)) {
			return;
		}

		var originals = {};
		attributes.forEach(function (attribute) {
			originals[attribute] = element.getAttribute(attribute);
		});
		element[ORIGINALS] = originals;
	};
	var saveOriginalBackgroundStyle = function saveOriginalBackgroundStyle(element) {
		if (hasOriginalAttrs(element)) {
			return;
		}

		element[ORIGINALS] = {
			backgroundImage: element.style.backgroundImage
		};
	}; // ## RESTORE ##

	var setOrResetAttribute = function setOrResetAttribute(element, attrName, value) {
		if (!value) {
			element.removeAttribute(attrName);
			return;
		}

		element.setAttribute(attrName, value);
	};

	var restoreOriginalAttrs = function restoreOriginalAttrs(element, attributes) {
		if (!hasOriginalAttrs(element)) {
			return;
		}

		var originals = getOriginalAttrs(element);
		attributes.forEach(function (attribute) {
			setOrResetAttribute(element, attribute, originals[attribute]);
		});
	};
	var restoreOriginalBgImage = function restoreOriginalBgImage(element) {
		if (!hasOriginalAttrs(element)) {
			return;
		}

		var originals = getOriginalAttrs(element);
		element.style.backgroundImage = originals.backgroundImage;
	};

	var manageApplied = function manageApplied(element, settings, instance) {
		addClass(element, settings.class_applied);
		setStatus(element, statusApplied); // Instance is not provided when loading is called from static class

		if (!instance) return;

		if (settings.unobserve_completed) {
			// Unobserve now because we can't do it on load
			unobserve(element, settings);
		}

		safeCallback(settings.callback_applied, element, instance);
	};
	var manageLoading = function manageLoading(element, settings, instance) {
		addClass(element, settings.class_loading);
		setStatus(element, statusLoading); // Instance is not provided when loading is called from static class

		if (!instance) return;
		updateLoadingCount(instance, +1);
		safeCallback(settings.callback_loading, element, instance);
	};
	var setAttributeIfValue = function setAttributeIfValue(element, attrName, value) {
		if (!value) {
			return;
		}

		element.setAttribute(attrName, value);
	};
	var setImageAttributes = function setImageAttributes(element, settings) {
		setAttributeIfValue(element, SIZES, getData(element, settings.data_sizes));
		setAttributeIfValue(element, SRCSET, getData(element, settings.data_srcset));
		setAttributeIfValue(element, SRC, getData(element, settings.data_src));
	};
	var setSourcesImg = function setSourcesImg(imgEl, settings) {
		forEachPictureSource(imgEl, function (sourceTag) {
			setOriginalsObject(sourceTag, attrsSrcSrcsetSizes);
			setImageAttributes(sourceTag, settings);
		});
		setOriginalsObject(imgEl, attrsSrcSrcsetSizes);
		setImageAttributes(imgEl, settings);
	};
	var setSourcesIframe = function setSourcesIframe(iframe, settings) {
		setOriginalsObject(iframe, attrsSrc);
		setAttributeIfValue(iframe, SRC, getData(iframe, settings.data_src));
	};
	var setSourcesVideo = function setSourcesVideo(videoEl, settings) {
		forEachVideoSource(videoEl, function (sourceEl) {
			setOriginalsObject(sourceEl, attrsSrc);
			setAttributeIfValue(sourceEl, SRC, getData(sourceEl, settings.data_src));
		});
		setOriginalsObject(videoEl, attrsSrcPoster);
		setAttributeIfValue(videoEl, POSTER, getData(videoEl, settings.data_poster));
		setAttributeIfValue(videoEl, SRC, getData(videoEl, settings.data_src));
		videoEl.load();
	};
	var setSourcesObject = function setSourcesObject(object, settings) {
		setOriginalsObject(object, attrsData);
		setAttributeIfValue(object, DATA, getData(object, settings.data_src));
	};
	var setBackground = function setBackground(element, settings, instance) {
		var bg1xValue = getData(element, settings.data_bg);
		var bgHiDpiValue = getData(element, settings.data_bg_hidpi);
		var bgDataValue = isHiDpi && bgHiDpiValue ? bgHiDpiValue : bg1xValue;
		if (!bgDataValue) return;
		element.style.backgroundImage = "url(\"".concat(bgDataValue, "\")");
		getTempImage(element).setAttribute(SRC, bgDataValue);
		manageLoading(element, settings, instance);
	}; // NOTE: THE TEMP IMAGE TRICK CANNOT BE DONE WITH data-multi-bg
	// BECAUSE INSIDE ITS VALUES MUST BE WRAPPED WITH URL() AND ONE OF THEM
	// COULD BE A GRADIENT BACKGROUND IMAGE

	var setMultiBackground = function setMultiBackground(element, settings, instance) {
		var bg1xValue = getData(element, settings.data_bg_multi);
		var bgHiDpiValue = getData(element, settings.data_bg_multi_hidpi);
		var bgDataValue = isHiDpi && bgHiDpiValue ? bgHiDpiValue : bg1xValue;

		if (!bgDataValue) {
			return;
		}

		element.style.backgroundImage = bgDataValue;
		manageApplied(element, settings, instance);
	};
	var setImgsetBackground = function setImgsetBackground(element, settings, instance) {
		var bgImgSetDataValue = getData(element, settings.data_bg_set);

		if (!bgImgSetDataValue) {
			return;
		}

		var imgSetValues = bgImgSetDataValue.split("|");
		var bgImageValues = imgSetValues.map(function (value) {
			return "image-set(".concat(value, ")");
		});
		element.style.backgroundImage = bgImageValues.join(); // Temporary fix for Chromeium with the -webkit- prefix

		if (element.style.backgroundImage === '') {
			bgImageValues = imgSetValues.map(function (value) {
				return "-webkit-image-set(".concat(value, ")");
			});
			element.style.backgroundImage = bgImageValues.join();
		}

		manageApplied(element, settings, instance);
	};
	var setSourcesFunctions = {
		IMG: setSourcesImg,
		IFRAME: setSourcesIframe,
		VIDEO: setSourcesVideo,
		OBJECT: setSourcesObject
	};
	var setSourcesNative = function setSourcesNative(element, settings) {
		var setSourcesFunction = setSourcesFunctions[element.tagName];

		if (!setSourcesFunction) {
			return;
		}

		setSourcesFunction(element, settings);
	};
	var setSources = function setSources(element, settings, instance) {
		var setSourcesFunction = setSourcesFunctions[element.tagName];

		if (!setSourcesFunction) {
			return;
		}

		setSourcesFunction(element, settings);
		manageLoading(element, settings, instance);
	};

	var elementsWithLoadEvent = ["IMG", "IFRAME", "VIDEO", "OBJECT"];
	var hasLoadEvent = function hasLoadEvent(element) {
		return elementsWithLoadEvent.indexOf(element.tagName) > -1;
	};
	var checkFinish = function checkFinish(settings, instance) {
		if (instance && !isSomethingLoading(instance) && !haveElementsToLoad(instance)) {
			safeCallback(settings.callback_finish, instance);
		}
	};
	var addEventListener = function addEventListener(element, eventName, handler) {
		element.addEventListener(eventName, handler);
		element.llEvLisnrs[eventName] = handler;
	};
	var removeEventListener = function removeEventListener(element, eventName, handler) {
		element.removeEventListener(eventName, handler);
	};
	var hasEventListeners = function hasEventListeners(element) {
		return !!element.llEvLisnrs;
	};
	var addEventListeners = function addEventListeners(element, loadHandler, errorHandler) {
		if (!hasEventListeners(element)) element.llEvLisnrs = {};
		var loadEventName = element.tagName === "VIDEO" ? "loadeddata" : "load";
		addEventListener(element, loadEventName, loadHandler);
		addEventListener(element, "error", errorHandler);
	};
	var removeEventListeners = function removeEventListeners(element) {
		if (!hasEventListeners(element)) {
			return;
		}

		var eventListeners = element.llEvLisnrs;

		for (var eventName in eventListeners) {
			var handler = eventListeners[eventName];
			removeEventListener(element, eventName, handler);
		}

		delete element.llEvLisnrs;
	};
	var doneHandler = function doneHandler(element, settings, instance) {
		deleteTempImage(element);
		updateLoadingCount(instance, -1);
		decreaseToLoadCount(instance);
		removeClass(element, settings.class_loading);

		if (settings.unobserve_completed) {
			unobserve(element, instance);
		}
	};
	var loadHandler = function loadHandler(event, element, settings, instance) {
		var goingNative = hasStatusNative(element);
		doneHandler(element, settings, instance);
		addClass(element, settings.class_loaded);
		setStatus(element, statusLoaded);
		safeCallback(settings.callback_loaded, element, instance);
		if (!goingNative) checkFinish(settings, instance);
	};
	var errorHandler = function errorHandler(event, element, settings, instance) {
		var goingNative = hasStatusNative(element);
		doneHandler(element, settings, instance);
		addClass(element, settings.class_error);
		setStatus(element, statusError);
		safeCallback(settings.callback_error, element, instance);
		if (settings.restore_on_error) restoreOriginalAttrs(element, attrsSrcSrcsetSizes);
		if (!goingNative) checkFinish(settings, instance);
	};
	var addOneShotEventListeners = function addOneShotEventListeners(element, settings, instance) {
		var elementToListenTo = getTempImage(element) || element;

		if (hasEventListeners(elementToListenTo)) {
			// This happens when loading is retried twice
			return;
		}

		var _loadHandler = function _loadHandler(event) {
			loadHandler(event, element, settings, instance);
			removeEventListeners(elementToListenTo);
		};

		var _errorHandler = function _errorHandler(event) {
			errorHandler(event, element, settings, instance);
			removeEventListeners(elementToListenTo);
		};

		addEventListeners(elementToListenTo, _loadHandler, _errorHandler);
	};

	var loadBackground = function loadBackground(element, settings, instance) {
		addTempImage(element);
		addOneShotEventListeners(element, settings, instance);
		saveOriginalBackgroundStyle(element);
		setBackground(element, settings, instance);
		setMultiBackground(element, settings, instance);
		setImgsetBackground(element, settings, instance);
	};

	var loadRegular = function loadRegular(element, settings, instance) {
		addOneShotEventListeners(element, settings, instance);
		setSources(element, settings, instance);
	};

	var load = function load(element, settings, instance) {
		if (hasLoadEvent(element)) {
			loadRegular(element, settings, instance);
		} else {
			loadBackground(element, settings, instance);
		}
	};
	var loadNative = function loadNative(element, settings, instance) {
		element.setAttribute("loading", "lazy");
		addOneShotEventListeners(element, settings, instance);
		setSourcesNative(element, settings);
		setStatus(element, statusNative);
	};

	var removeImageAttributes = function removeImageAttributes(element) {
		element.removeAttribute(SRC);
		element.removeAttribute(SRCSET);
		element.removeAttribute(SIZES);
	};

	var resetSourcesImg = function resetSourcesImg(element) {
		forEachPictureSource(element, function (sourceTag) {
			removeImageAttributes(sourceTag);
		});
		removeImageAttributes(element);
	};

	var restoreImg = function restoreImg(imgEl) {
		forEachPictureSource(imgEl, function (sourceEl) {
			restoreOriginalAttrs(sourceEl, attrsSrcSrcsetSizes);
		});
		restoreOriginalAttrs(imgEl, attrsSrcSrcsetSizes);
	};
	var restoreVideo = function restoreVideo(videoEl) {
		forEachVideoSource(videoEl, function (sourceEl) {
			restoreOriginalAttrs(sourceEl, attrsSrc);
		});
		restoreOriginalAttrs(videoEl, attrsSrcPoster);
		videoEl.load();
	};
	var restoreIframe = function restoreIframe(iframeEl) {
		restoreOriginalAttrs(iframeEl, attrsSrc);
	};
	var restoreObject = function restoreObject(objectEl) {
		restoreOriginalAttrs(objectEl, attrsData);
	};
	var restoreFunctions = {
		IMG: restoreImg,
		IFRAME: restoreIframe,
		VIDEO: restoreVideo,
		OBJECT: restoreObject
	};

	var restoreAttributes = function restoreAttributes(element) {
		var restoreFunction = restoreFunctions[element.tagName];

		if (!restoreFunction) {
			restoreOriginalBgImage(element);
			return;
		}

		restoreFunction(element);
	};

	var resetClasses = function resetClasses(element, settings) {
		if (hasEmptyStatus(element) || hasStatusNative(element)) {
			return;
		}

		removeClass(element, settings.class_entered);
		removeClass(element, settings.class_exited);
		removeClass(element, settings.class_applied);
		removeClass(element, settings.class_loading);
		removeClass(element, settings.class_loaded);
		removeClass(element, settings.class_error);
	};

	var restore = function restore(element, settings) {
		restoreAttributes(element);
		resetClasses(element, settings);
		resetStatus(element);
		deleteOriginalAttrs(element);
	};

	var cancelLoading = function cancelLoading(element, entry, settings, instance) {
		if (!settings.cancel_on_exit) return;
		if (!hasStatusLoading(element)) return;
		if (element.tagName !== "IMG") return; //Works only on images

		removeEventListeners(element);
		resetSourcesImg(element);
		restoreImg(element);
		removeClass(element, settings.class_loading);
		updateLoadingCount(instance, -1);
		resetStatus(element);
		safeCallback(settings.callback_cancel, element, entry, instance);
	};

	var onEnter = function onEnter(element, entry, settings, instance) {
		var dontLoad = hadStartedLoading(element);
		/* Save status 
		before setting it, to prevent loading it again. Fixes #526. */

		setStatus(element, statusEntered);
		addClass(element, settings.class_entered);
		removeClass(element, settings.class_exited);
		unobserveEntered(element, settings, instance);
		safeCallback(settings.callback_enter, element, entry, instance);
		if (dontLoad) return;
		load(element, settings, instance);
	};
	var onExit = function onExit(element, entry, settings, instance) {
		if (hasEmptyStatus(element)) return; //Ignore the first pass, at landing

		addClass(element, settings.class_exited);
		cancelLoading(element, entry, settings, instance);
		safeCallback(settings.callback_exit, element, entry, instance);
	};

	var tagsWithNativeLazy = ["IMG", "IFRAME", "VIDEO"];
	var shouldUseNative = function shouldUseNative(settings) {
		return settings.use_native && "loading" in HTMLImageElement.prototype;
	};
	var loadAllNative = function loadAllNative(elements, settings, instance) {
		elements.forEach(function (element) {
			if (tagsWithNativeLazy.indexOf(element.tagName) === -1) {
				return;
			}

			loadNative(element, settings, instance);
		});
		setToLoadCount(instance, 0);
	};

	var isIntersecting = function isIntersecting(entry) {
		return entry.isIntersecting || entry.intersectionRatio > 0;
	};

	var getObserverSettings = function getObserverSettings(settings) {
		return {
			root: settings.container === document ? null : settings.container,
			rootMargin: settings.thresholds || settings.threshold + "px"
		};
	};

	var intersectionHandler = function intersectionHandler(entries, settings, instance) {
		entries.forEach(function (entry) {
			return isIntersecting(entry) ? onEnter(entry.target, entry, settings, instance) : onExit(entry.target, entry, settings, instance);
		});
	};

	var observeElements = function observeElements(observer, elements) {
		elements.forEach(function (element) {
			observer.observe(element);
		});
	};
	var updateObserver = function updateObserver(observer, elementsToObserve) {
		resetObserver(observer);
		observeElements(observer, elementsToObserve);
	};
	var setObserver = function setObserver(settings, instance) {
		if (!supportsIntersectionObserver || shouldUseNative(settings)) {
			return;
		}

		instance._observer = new IntersectionObserver(function (entries) {
			intersectionHandler(entries, settings, instance);
		}, getObserverSettings(settings));
	};

	var toArray = function toArray(nodeSet) {
		return Array.prototype.slice.call(nodeSet);
	};
	var queryElements = function queryElements(settings) {
		return settings.container.querySelectorAll(settings.elements_selector);
	};
	var excludeManagedElements = function excludeManagedElements(elements) {
		return toArray(elements).filter(hasEmptyStatus);
	};
	var hasError = function hasError(element) {
		return hasStatusError(element);
	};
	var filterErrorElements = function filterErrorElements(elements) {
		return toArray(elements).filter(hasError);
	};
	var getElementsToLoad = function getElementsToLoad(elements, settings) {
		return excludeManagedElements(elements || queryElements(settings));
	};

	var retryLazyLoad = function retryLazyLoad(settings, instance) {
		var errorElements = filterErrorElements(queryElements(settings));
		errorElements.forEach(function (element) {
			removeClass(element, settings.class_error);
			resetStatus(element);
		});
		instance.update();
	};
	var setOnlineCheck = function setOnlineCheck(settings, instance) {
		if (!runningOnBrowser) {
			return;
		}

		instance._onlineHandler = function () {
			retryLazyLoad(settings, instance);
		};

		window.addEventListener("online", instance._onlineHandler);
	};
	var resetOnlineCheck = function resetOnlineCheck(instance) {
		if (!runningOnBrowser) {
			return;
		}

		window.removeEventListener("online", instance._onlineHandler);
	};

	var LazyLoad = function LazyLoad(customSettings, elements) {
		var settings = getExtendedSettings(customSettings);
		this._settings = settings;
		this.loadingCount = 0;
		setObserver(settings, this);
		setOnlineCheck(settings, this);
		this.update(elements);
	};

	LazyLoad.prototype = {
		update: function update(givenNodeset) {
			var settings = this._settings;
			var elementsToLoad = getElementsToLoad(givenNodeset, settings);
			setToLoadCount(this, elementsToLoad.length);

			if (isBot || !supportsIntersectionObserver) {
				this.loadAll(elementsToLoad);
				return;
			}

			if (shouldUseNative(settings)) {
				loadAllNative(elementsToLoad, settings, this);
				return;
			}

			updateObserver(this._observer, elementsToLoad);
		},
		destroy: function destroy() {
			// Observer
			if (this._observer) {
				this._observer.disconnect();
			} // Clean handlers


			resetOnlineCheck(this); // Clean custom attributes on elements

			queryElements(this._settings).forEach(function (element) {
				deleteOriginalAttrs(element);
			}); // Delete all internal props

			delete this._observer;
			delete this._settings;
			delete this._onlineHandler;
			delete this.loadingCount;
			delete this.toLoadCount;
		},
		loadAll: function loadAll(elements) {
			var _this = this;

			var settings = this._settings;
			var elementsToLoad = getElementsToLoad(elements, settings);
			elementsToLoad.forEach(function (element) {
				unobserve(element, _this);
				load(element, settings, _this);
			});
		},
		restoreAll: function restoreAll() {
			var settings = this._settings;
			queryElements(settings).forEach(function (element) {
				restore(element, settings);
			});
		}
	};

	LazyLoad.load = function (element, customSettings) {
		var settings = getExtendedSettings(customSettings);
		load(element, settings);
	};

	LazyLoad.resetStatus = function (element) {
		resetStatus(element);
	}; // Automatic instances creation if required (useful for async script loading)


	if (runningOnBrowser) {
		autoInitialize(LazyLoad, window.lazyLoadOptions);
	}

	return LazyLoad;

})));
;
(function (angular) {
	'use strict';

	var module = angular.module('haAccordionModule', []);

	module.directive('haAccordion', function () {
		return {
			restrict: 'A',
			link: function ($scope, element, attrs) {
				var linkPanels = attrs["linkPanels"]; // Should the panels close when another is opened?
				var $allPanels = element.find('[accordion-panel]');
				var animating = false;
				$allPanels.each(function(i, $panel) {
					$panel = angular.element($panel);
					var $panelHeader = $panel.find('[accordion-panel-header]');
					var $panelBody = $panel.find('[accordion-panel-body]');
					$panelHeader.click(function(e) {
						if (animating) {
							return;
						}
						animating = true;
						if (linkPanels) {
							$allPanels.not($panel).addClass('collapsed').removeClass('expanded').find('[accordion-panel-body]').slideUp({
								duration: 300,
								queue: false
							});
						}
						$panelBody.slideToggle({
							duration: 300,
							queue: false,
							start: function() {
								$panel.toggleClass('expanded');
								$panel.toggleClass('collapsed');
							},
							always: function() {
								animating = false;
							}
						});
					});
				});
			}
		};
	});

})(angular);
;
(function (angular) {

	// Ha Currency Directive
	// --------------------------------------------
	//
	// * **Class:** HaCurrency
	// * **Author:** Josh Nielsen
	//
	// A filter for currency that formats it for different countries

	'use strict';

	var module = angular.module('haCurrencyModule', []);

	module.filter('localCurrency', ['$compile', function () {
		var types = {
			USD: ' <span class="currency-type">USD</span>',
			AUD: ' <span class="currency-type">AUD</span>',
			NZD: ' <span class="currency-type">NZD</span>',
			TWD: '<span class="currency-type">NT</span>',
			MILES: ' <span class="currency-type">' + angular.element('#SC_MilesText_Display').html() + '</span>'
		};

		var formats = {
			USD: '<span class="currency-symbol">$</span>{{ amount }}{0}',
			AUD: '<span class="currency-symbol">$</span>{{ amount }}{0}',
			NZD: '<span class="currency-symbol">$</span>{{ amount }}{0}',
			CNY: '<span class="currency-symbol">¥</span>{{ amount }}',
			KRW: '<span class="currency-symbol">₩</span>{{ amount }}',
			JPY: '<span class="currency-symbol">¥</span>{{ amount }}',
			TWD: '{0}<span class="currency-symbol">$</span>{{ amount }}',
			MILES: '{{ amount }}{0}'
		};

		var replaceNumberWithCommas = function (yourNumber) {
			//Seperates the components of the number
			var n = yourNumber.toString().split('.');
			//Comma-fies the first part
			n[0] = n[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
			//Combines the two sections
			return n.join('.');
		};

		var signCurrency = function (rawAmount, formatted) {
			if (rawAmount > -1) {
				return formatted + ' ' + angular.element('#MoreText').val();
			} else {
				return formatted.replace(/-([1-9]+)/g, function ($1, $2) {
						return $2;
					}) + ' ' + angular.element('#LessText').val();
			}
		};

		var localRoundUp = function (amount, code) {
			if (code === 'CNY') {
				return Math.ceil(amount / 10) * 10;
			}
			else if (code === 'KRW' || code === 'JPY') {
				return Math.ceil(amount / 100) * 100;
			} else {
				return Math.ceil(amount);
			}
		}

		return function (amount, code, noDecimal, signed, disableRoundOff, disableType, roundUp, needsUSDLabel) {
			var rounded;
			var commaFormatted;

			//validate decimal round off based on currency.
			if (code && !noDecimal) {
				noDecimal = ('USD AUD NZD'.indexOf(code.toUpperCase()) === -1);
			}

			if (!disableRoundOff) {
				if (noDecimal) {
					rounded = (roundUp) ? localRoundUp(amount, code) : Math.round(amount);
				} else {
					rounded = (Math.round(amount * 100) / 100).toFixed(2);
				}
			}
			else {
				rounded = amount;
			}

			if (typeof (rounded) !== 'undefined' && rounded != null) {
				commaFormatted = replaceNumberWithCommas(rounded);
			}

			// get rid of decimal places if MILES
			if (code === 'MILES') {
				commaFormatted = commaFormatted.split('.')[0];
			}

			if (formats[code]) {
				// Add or omit currency type
				var typeFormatted = formats[code].format(disableType ? '' : (types[code] || ''));
				if (code == 'USD' && !needsUSDLabel) {
					typeFormatted = formats[code].format('');
				}
				// Inject amount
				var denominationFormatted = typeFormatted.replace(/\{\{.*\}\}/, commaFormatted);
				if (signed) {
					return signCurrency(amount, denominationFormatted);
				} else {
					//console.log(denominationFormatted);
					return denominationFormatted;
				}
			}
		};
	}]);

	module.filter('superCurrency', [
		'$log',
		'$filter',
		'$locale',
		'$sce',

		function ($log, $filter, $locale, $sce) {
			var formats = {
				USD: '<span class="currency-symbol">$</span><span class="currency-dollars">{{ dollars }}</span><span class="currency-cents">{{ cents }}</span>',
				AUD: '<span class="currency-symbol">$</span><span class="currency-dollars">{{ dollars }}</span><span class="currency-cents">{{ cents }}</span> <span class="currency-type">AUD</span>',
				NZD: '<span class="currency-symbol">$</span><span class="currency-dollars">{{ dollars }}</span><span class="currency-cents">{{ cents }}</span> <span class="currency-type">NZD</span>',
				CNY: '<span class="currency-symbol">¥</span><span class="currency-dollars">{{ dollars }}</span><span class="currency-cents">{{ cents }}</span>',
				KRW: '<span class="currency-symbol">₩</span><span class="currency-dollars">{{ dollars }}</span><span class="currency-cents">{{ cents }}</span>',
				JPY: '<span class="currency-symbol">¥</span><span class="currency-dollars">{{ dollars }}</span><span class="currency-cents">{{ cents }}</span>',
				TWD: '<span class="currency-type">NT</span><span class="currency-symbol">$</span><span class="currency-dollars">{{ dollars }}</span><span class="currency-cents">{{ cents }}</span>'
			};

			var buildHtml = function (code, dollars, cents) {
				var html = formats[code].replace('{{ dollars }}', dollars).replace('{{ cents }}', cents);
				return $sce.trustAsHtml(html);
			};

			return function (amount, code, noDecimal) {
				// $log.debug('superCurrency:', arguments);
				code = code || 'USD';
				var formats = $locale.NUMBER_FORMATS;
				var formattedAmount = $filter('currency')(amount, '');

				// Extract "dollars" and "cents" parts
				var dollars = formattedAmount.substring(0, formattedAmount.indexOf(formats.DECIMAL_SEP));
				var cents = formattedAmount.substring(formattedAmount.indexOf(formats.DECIMAL_SEP));

				if (noDecimal === true) {
					cents = '';
				}

				// In 'roundPrice' mode, display cents if no whole dollars
				if ((noDecimal === 'roundPrice') && (amount >= 1.0 || amount <= -1.0)) {
					cents = '';
				}

				// In 'roundSavings' mode, display cents if no whole dollars
				if ((noDecimal === 'roundSavings') && (amount >= 1.0 || amount <= -1.0)) {
					cents = '';
				}

				return buildHtml(code, dollars, cents);
			};
		}
	]);

	// currency without any fractional values.
	module.filter('noFractionCurrency', ['$filter', '$locale',	function(filter, locale) {
		var currencyFilter = filter('currency');
		var formats = locale.NUMBER_FORMATS;
		return function(amount, currencySymbol) {
			var value = currencyFilter(amount, currencySymbol);
			var sep = value.indexOf(formats.DECIMAL_SEP);
			return value.substring(0, sep);
		};
	}]);

	// makes view value be prefixed with currency symbol, model value without.
	// also restricts value to whole numbers
	module.directive('wholeCurrency', function () {
	    return {
	        require: 'ngModel',
	        link: function(scope, elem, attrs, modelCtrl) {
	        	var currencyCode = scope.$root.$currency;
	        	var currencySymbols = {
					USD: '$',
					AUD: '$',
					NZD: '$',
					CNY: '¥',
					KRW: '₩',
					JPY: '¥',
					TWD: 'NT$'
				};

	        	function formatDisplay(val){
	        		if (val) {
		            	val = val+'';
		            	if (val.match(/\d/g)) {
			            	var wholeNumber = Number(val.match(/\d/g).join('')).toFixed();
					        var withCurrency = currencySymbols[currencyCode] +''+ wholeNumber;

			            	if (withCurrency != val) {
				            	modelCtrl.$setViewValue(withCurrency);
				            	modelCtrl.$render();
				            }
			                return parseInt(wholeNumber);
			            } else {

			            	if (currencySymbols[currencyCode] != val) {
				            	modelCtrl.$setViewValue(currencySymbols[currencyCode]);
				            	modelCtrl.$render();
				            }
			            	return 0;
			            }
			        }
	            }
	            function formatModel(val){
	        		return currencySymbols[currencyCode] + val;
	            }
	            modelCtrl.$parsers.unshift(formatDisplay);
	            modelCtrl.$formatters.unshift(formatModel); // format value with currency on page load
	        }
	    }
	});

})(angular);
;
(function (angular) {

	// Ha Localize Name Directive
	// --------------------------------------------
	//
	// * **Class:** HaLocalizeName
	// * **Author:** Cinthia Miller
	//
	// Filters for names and dates that formats it for different countries

	'use strict';

	var module = angular.module('haLocalizeNameModule', []);

	module.filter('localName', [function () {
		var formats = {
			'en': '{{ firstName }} {{ lastName }}',
			'en-au': '{{ firstName }} {{ lastName }}',
			'en-nz': '{{ firstName }} {{ lastName }}',
			'zh-cn': '{{ lastName }} {{ firstName }}',
			'ko-kr': '{{ lastName }} {{ firstName }}',
			'ja-jp': '{{ lastName }} {{ firstName }}',
			'zh-tw': '{{ lastName }} {{ firstName }}'
		};

		return function (firstName, lastName, code) {

			if (formats[code]) {
				var localFormatted = formats[code].replace(/\{\{ firstName \}\}/, firstName);
				localFormatted = localFormatted.replace(/\{\{ lastName \}\}/, lastName);
				return localFormatted;
			}
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Localize Date Directives
	// --------------------------------------------
	//
	// * **Class:** HaLocalizeDates
	// * **Author:** Cinthia Miller
	//
	// A filter for currency that formats it for different countries

	'use strict';

	var module = angular.module('haLocalizeDateModule', []);

	module.filter('localDate', ['$filter', function ($filter) {
		var formats = {
			'en': 'EEE M/d',
			'en-au': 'EEE M/d',
			'en-nz': 'EEE M/d',
			'zh-cn': 'M月d日 EEE',
			'ko-kr': 'M월 d일 EEE',
			'ja-jp': 'M月d日(EEE)',
			'zh-tw': 'M月d日 EEE'
		};

		return function (date, code) {
			var typeDate = new Date();
			var standardDateFilterFn = $filter('date');

			if (formats[code]) {
				if (date instanceof Date) {
					typeDate = date;
				}
				else {
					typeDate = new Date(date);
				}

				return standardDateFilterFn(typeDate, formats[code]);
			}
		};
	}]);

	module.filter('localShortDate', ['$filter', function ($filter) {
		var formats = {
			'en': 'MM/dd/yyyy',
			'en-au': 'MM/dd/yyyy',
			'en-nz': 'MM/dd/yyyy',
			'zh-cn': 'yyyy/MM/dd',
			'ko-kr': 'yyyy/MM/dd',
			'ja-jp': 'yyyy/MM/dd',
			'zh-tw': 'yyyy/MM/dd'
		};

		return function (date, code) {
			var typeDate = new Date();
			var standardDateFilterFn = $filter('date');

			if (formats[code]) {
				if (date instanceof Date) {
					typeDate = date;
				}
				else if (/^\d{4}-\d{1,2}-\d{1,2}/gi.test(date)) {
					var dateSplit = date.split('T')[0].split('-');

					typeDate = (dateSplit.length === 3) ? new Date(parseInt(dateSplit[0], 10), parseInt(dateSplit[1], 10) - 1, parseInt(dateSplit[2], 10)) : new Date();
				} else {
					typeDate = new Date(date);
				}

				return standardDateFilterFn(typeDate, formats[code]);
			}
		};
	}]);

	module.filter('dateWithDayButNoYear', ['$filter', function ($filter) {
		var defaultFormat = 'EEE, MMM d';
		var formats = {
			'en': 'EEE, MMM d',
			'en-au': 'EEE, MMM d',
			'en-nz': 'EEE, MMM d',
			'zh-cn': 'M月d日 EEE',
			'ko-kr': 'M월 d일 EEE',
			'ja-jp': 'M月d日(EEE)',
			'zh-tw': 'M月d日 EEE'
		};

		return function (date, code) {
			return $filter('date')(date, formats[code] || defaultFormat);
		};
	}]);

	module.filter('localShortDateWithMonthandDay', ['$filter', function ($filter) {
		var defaultFormat = 'MMM d';
		var formats = {
			'en': 'MMM d',
			'en-au': 'MMM d',
			'en-nz': 'MMM d',
			'zh-cn': 'M月d日',
			'ko-kr': 'M월 d일',
			'ja-jp': 'M月d日',
			'zh-tw': 'M月d日'
		};

		return function (date, code) {
			return $filter('date')(date, formats[code] || defaultFormat);
		};
	}]);

	module.filter('localTime', ['$filter', function ($filter) {
		var defaultFormat = 'hh:mma';
		var formats = {
			'en': 'h:mma',
			'en-au': 'h:mma',
			'en-nz': 'h:mma',
			'zh-cn': 'H:mm',
			'ko-kr': 'H:mm',
			'ja-jp': 'H:mm',
			'zh-tw': 'H:mm'
		};


		return function (time, code) {
			return $filter('date')(time, formats[code] || defaultFormat);
		};

	}]);

	//Sun Jan 16,2021
	module.filter('localFullDateAbbreviated', ['$filter', function ($filter) {
		var defaultFormat = 'EEE MMM d,yyyy';
		var formats = {
			'en': 'EEE MMM d,yyyy'
		};

		return function (date, code) {
			return $filter('date')(date, formats[code] || defaultFormat);
		};
	}]);

})(angular);
;
(function (angular) {

	// Currency No Decimals Directive
	// --------------------------------------------
	//
	// * **Class:** CurrencyNoDecimals
	// * **Author:** Cory Shaw
	//
	// Formats number as currency and removes decimals

	'use strict';

	var module = angular.module('currencyNoDecimalsFilter', []);

	module.filter('currencyNoDecimalsFilter', ['$filter', '$locale', function (filter, locale) {
		var currencyFilter = filter('currency');
		var formats = locale.NUMBER_FORMATS;
		return function (amount, currencySymbol) {
			var value = currencyFilter(amount, currencySymbol);
			var sep = value.indexOf(formats.DECIMAL_SEP);
			if (amount >= 0) {
				return value.substring(0, sep);
			}
			return value.substring(0, sep) + ')';
		};
	}]);

})(angular);
;
(function (angular) {

	// Ha Rounding Filters
	// --------------------------------------------
	//
	// * **Class:** HARoundingFilters
	// * **Author:** Nathan Probst
	//
	// Filters for rounding prices and savings.

	'use strict';

	var module = angular.module('haRoundingFiltersModule', []);

	module.filter('roundPrice', [function () {
		return function (price) {
			// If more than $1.00, round up
			if ((price > 1.0) || (price < -1.0)) {
				return Math.ceil(price);
			}
			// Else leave unchanged
			return price;
		};
	}]);

	module.filter('roundSavings', [function () {
		return function (price) {
			// If more than $1.00, round down
			if ((price > 1.0) || (price < -1.0)) {
				return Math.floor(price);
			}
			// Else leave unchanged
			return price;
		};
	}]);

})(angular);
;
(function (angular) {

	// Title Case Directive
	// --------------------------------------------
	//
	// * **Class:** TitleCase
	// * **Author:** Francis Oyeniyi
	//
	// Returns String as title case

	'use strict';

	var module = angular.module('TitleCaseModule', []);

	module.filter('TitleCase', [function () {

		return function titleCase(str) {
			return str.toLowerCase().split(' ').map(function (word, index) {
				// add more as needed
				return (((word == "or" ||
						word == "and" ||
						word == "but") &&
					index != 0) ? word  : word.charAt(0).toUpperCase() + word.slice(1));
			}).join(' ');
		}
	}]);

})(angular);
;
(function (angular) {

	// Ha Homepage
	// --------------------------------------------
	//
	// * **Class:** HaHomepage
	// * **Author:** Cory Shaw
	//
	// The Homepage

	'use strict';

	var module = angular.module('haHomepageModule', []);

	module.controller('haHomepageCtrl', ['$rootScope', '$scope', '$window', 'haLaunchDarklyAPI', function ($rootScope, $scope, $window, halaunchDarklyAPI) {

		$scope.$emit('$haHomepageReady');

		$scope.departureDateToday = (typeof $window.serverShortDate !== 'undefined') ? new Date($window.serverShortDate) : new Date();
		$scope.departureDateTomorrow = (typeof $window.serverShortDate !== 'undefined') ? new Date($window.serverShortDate) : new Date();
		$scope.departureDateYesterday = (typeof $window.serverShortDate !== 'undefined') ? new Date($window.serverShortDate) : new Date();
		$scope.departureDateTomorrow.setDate($scope.departureDateToday.getDate() + 1);
		$scope.departureDateYesterday.setDate($scope.departureDateToday.getDate() - 1);
		$scope.tab = 'bookflights';
		$scope.flightStatusType = 'byFlightNumber';
		$scope.flightStatusSpaEnabled = false;
		$scope.expanded = false;

		halaunchDarklyAPI.getFeatureFlag(HA.FLAGS.FlightStatusSpaEnabledKey, 'boolean', true, true).then(
			function (response) {
				if (response.data === "ENABLED")
				{
					$scope.flightStatusSpaEnabled = true;
				}
				else {
					return null;
				}
			});

		$scope.$on('haFormValidationSuccess', function (currentScope, args) {
			if (args.formName === 'flightStatusByNumber') {
				if (typeof (Storage) !== 'undefined') {
					sessionStorage.FlightNumber = args.formScope.FlightNumber.$modelValue;
					sessionStorage.selectedDepartureDateID = $('#flightStatusByNumber div[ha-radio] input:checked').attr('id');
				}
			}
		});

		$scope.$on('cartrawlerFormsLoaded', function() {
			$scope.cartrawlerFormsLoaded = true;
		});

		var loaded = false;
		$scope.$watch('tab', function (newVal) {
			if (!loaded) {
				loaded = true;
				return;
			}
			if (newVal === 'flightstatus') {
				var date = new Date();
				$rootScope.DepartureDate = [date.getMonth() + 1, date.getDate(), date.getFullYear()].join('/');
			}
		});

		$scope.submitItineraryLookup = function (Form) {
			if (Form.$valid) {
				$scope.disableItineraryLookupBtn = true;
				$(Form).submit();
			}
		};
	}]);

})(angular);
;
(function (angular) {

	'use strict';

	var module = angular.module('formValidation', []);

	module.directive('showErrors', function ($timeout, showErrorsConfig) {
		var getShowSuccess;
		var linkFn;
		getShowSuccess = function (options) {
			var showSuccess;
			showSuccess = showErrorsConfig.showSuccess;
			if (options && options.showSuccess != null) {
				showSuccess = options.showSuccess;
			}
			return showSuccess;
		};
		linkFn = function (scope, el, attrs, formCtrl) {
			var blurred = false;
			var options = scope.$eval(attrs.showErrors);
			var showSuccess = getShowSuccess(options);
			var inputEl = el[0].querySelector('[name]');
			var inputNgEl = angular.element(inputEl);
			var inputName = inputNgEl.attr('name');
			if (!inputName) {
				throw 'show-errors element has no child input elements with a \'name\' attribute';
			}
			inputNgEl.bind('blur', function () {
				blurred = true;
				return toggleClasses(formCtrl[inputName].$invalid);
			});
			scope.$watch(function () {
				return formCtrl[inputName] && formCtrl[inputName].$invalid;
			}, function (invalid) {
				if (!blurred) {
					return;
				}
				return toggleClasses(invalid);
			});
			scope.$on('show-errors-check-validity', function () {
				return toggleClasses(formCtrl[inputName].$invalid);
			});

			var toggleClasses = function (invalid) {
				el.toggleClass('has-error', invalid);
				if (showSuccess) {
					return el.toggleClass('has-success', !invalid);
				}
			};

			return toggleClasses;
		};
		return {
			restrict: 'A',
			require: '^form',
			compile: function (elem) {
				if (!elem.hasClass('form-group')) {
					throw 'show-errors element does not have the \'form-group\' class';
				}
				return linkFn;
			}
		};
	});

	module.provider('showErrorsConfig', function () {
		var show = false;
		this.showSuccess = function (showSuccess) {
			show = showSuccess;
			return show;
		};
		this.$get = function () {
			return {showSuccess: show};
		};
	});

	module.controller('formController', function ($scope) {
		$scope.save = function () {
			$scope.$broadcast('show-errors-check-validity');
		};

		$scope.saveHA = function () {
			$scope.$broadcast('show-errors-check-validity');
		};
	});

})(angular);



;
(function (angular) {

	'use strict';

	// Hawaiian Homepage Stories and Events
	// ===============================
	//
	// * **Class:** haHomeStoriesAndEvents
	// * **Author:** Jamie Perkins
	//
	// Miscellaneous functions needed for stories and events on homepage

	var module = angular.module('haHomepageStoriesAndEventsModule', []);

	module.controller('HomepageStoriesAndEventsController', [
		'$scope',
		'$locale',
		'haDateUtils',
		function ($scope, $locale, haDateUtils) {

			// takes datestring, returns 'SEP', but in locale specific string
			$scope.dateStringToMonthAbbrev = function (datestring) {
				var d = moment(datestring).toDate();
				var m = d.getMonth();
				return $locale.DATETIME_FORMATS.SHORTMONTH[m];
			};

			$scope.msToDate = haDateUtils.msToDate;

		}]);

})(angular);
;
