/* Minification failed. Returning unminified contents.
(8676,31-32): run-time error JS1003: Expected ':': (
(8676,51-52): run-time error JS1100: Expected ',': {
(8682,6-7): run-time error JS1195: Expected expression: ,
(8683,22-23): run-time error JS1007: Expected ']': :
(8683,41-42): run-time error JS1004: Expected ';': {
(8685,6-7): run-time error JS1195: Expected expression: ,
(8686,47-48): run-time error JS1004: Expected ';': {
(8689,6-7): run-time error JS1195: Expected expression: ,
(8690,45-46): run-time error JS1004: Expected ';': {
(8693,6-7): run-time error JS1195: Expected expression: ,
(8694,49-50): run-time error JS1004: Expected ';': {
(8697,6-7): run-time error JS1195: Expected expression: ,
(8698,48-49): run-time error JS1004: Expected ';': {
(8700,6-7): run-time error JS1195: Expected expression: ,
(8701,55-56): run-time error JS1004: Expected ';': {
(8705,6-7): run-time error JS1195: Expected expression: ,
(8706,52-53): run-time error JS1004: Expected ';': {
(8712,6-7): run-time error JS1195: Expected expression: ,
(8713,56-57): run-time error JS1004: Expected ';': {
(8720,6-7): run-time error JS1195: Expected expression: ,
(8721,63-64): run-time error JS1004: Expected ';': {
(8737,6-7): run-time error JS1195: Expected expression: ,
(8738,40-41): run-time error JS1004: Expected ';': {
(8741,6-7): run-time error JS1195: Expected expression: ,
(8742,53-54): run-time error JS1004: Expected ';': {
(8748,6-7): run-time error JS1195: Expected expression: ,
(8749,59-60): run-time error JS1004: Expected ';': {
(8755,6-7): run-time error JS1195: Expected expression: ,
(8756,50-51): run-time error JS1004: Expected ';': {
(8758,6-7): run-time error JS1195: Expected expression: ,
(8759,45-46): run-time error JS1004: Expected ';': {
(8763,6-7): run-time error JS1195: Expected expression: ,
(8764,48-49): run-time error JS1004: Expected ';': {
(8766,6-7): run-time error JS1195: Expected expression: ,
(8767,67-68): run-time error JS1004: Expected ';': {
(8772,6-7): run-time error JS1195: Expected expression: ,
(8773,78-79): run-time error JS1004: Expected ';': {
(8780,4-5): run-time error JS1002: Syntax error: }
(8780,6-7): run-time error JS1197: Too many errors. The file might not be a JavaScript file: ;
(8774,6,8778,8): run-time error JS1018: 'return' statement outside of function: return this.post("/registry/request-score-correction/", {
						comments: comments,
						setTypeID: setTypeID,
						selectedSlotCoins: selectedSlotCoins,
					})
(8768,6,8771,9): run-time error JS1018: 'return' statement outside of function: return Upload.upload({ url: "/registry/manage/sets/" + peopleSetID + "/cover-image/", data: { image: file, orientation: orientation } })
						.then(function(results) {
							return results.data;
						})
(8765,6-86): run-time error JS1018: 'return' statement outside of function: return this.post("registry/manage/sets/" + peopleSetID + "/cover-image/delete/")
(8762,6-29): run-time error JS1018: 'return' statement outside of function: return modifySetResults
(8757,6-86): run-time error JS1018: 'return' statement outside of function: return this.get("registry/manage/sets/" + peopleSetID + "/slot/" + slotID + "/")
(8750,6,8754,31): run-time error JS1018: 'return' statement outside of function: return this.post("registry/manage/sets/remove-from-inventory", {
						peopleCoinID: peopleCoinID,
						peopleSetID: slotData.peopleSetID,
						slotID: slotData.slotID
					}).then(this.refreshSlot)
(8743,6,8747,31): run-time error JS1018: 'return' statement outside of function: return this.post("registry/manage/sets/remove-from-set/", {
						peopleCoinID: peopleCoinID,
						peopleSetID	: slotData.peopleSetID,
						slotID		: slotData.slotID
					}).then(this.refreshSlot)
(8739,6,8740,30): run-time error JS1018: 'return' statement outside of function: return this.post("registry/manage/sets/add-to-sets/", addToSetsData)
						.then(this.refreshSlot)
(8722,6,8736,9): run-time error JS1018: 'return' statement outside of function: return this.post("registry/manage/sets/add-to-set/", {
							peopleCoinID	: peopleCoinID,
							collectibleID	: collectibleID,
							peopleSetID		: slotData.peopleSetID,
							slotID			: slotData.slotID,
							setName			: slotData.setName,
							requestNewSlot  : slotData.requestNewSlot,
						})
						.then(function(results) {
							if(!results.AddToPendingCoins) {
								$rootScope.$broadcast("set.refresh", results);
								$rootScope.$broadcast("user.refresh");
							}
							return results;
						})
(8714,6,8719,8): run-time error JS1018: 'return' statement outside of function: return this.post("registry/manage/sets/request-transfer/", {
						collectibleID	: collectibleID,
						peopleSetID		: slotData.peopleSetID,
						slotID			: slotData.slotID,
						setName			: slotData.setName
					})
(8707,6,8711,31): run-time error JS1018: 'return' statement outside of function: return this.post("registry/manage/sets/validate-add-to-slot/", {
						peopleCoinID: peopleCoinID,
						peopleSetID	: slotData.peopleSetID,
						slotID		: slotData.slotID
					}).then(this.refreshSlot)
(8704,6-33): run-time error JS1018: 'return' statement outside of function: return this.post(url, data)
(8699,6-83): run-time error JS1018: 'return' statement outside of function: return this.get("registry/competitive-sets/" + peopleSetID + "/nominations/")
(8696,6-26): run-time error JS1018: 'return' statement outside of function: return this.get(url)
(8692,6-26): run-time error JS1018: 'return' statement outside of function: return this.get(url)
(8688,6-26): run-time error JS1018: 'return' statement outside of function: return this.get(url)
(8684,6-93): run-time error JS1018: 'return' statement outside of function: return this.get("/resources/services/registry/competitive/slots/" + slotID + "/coins/")
 */
"use strict";
angular.module("NGC.Registry")
	.value("user", CCG.user)
	.controller("appController", [
		"$scope", "$window", "accountService", "user", "metadataService",
		function($scope, $window, accountService, user, metadataService) {
			var self = this;
			$scope.user = user;
			$scope.modals = {};

			self.openAddCoin = function(certNum) {
				if($scope.user.IsLoggedIn) {
					if($scope.user.UserType !== "Dealer") {
						$scope.$broadcast("addCoin.openLookup", {
							grader: certNum ? "NGC" : null,
							certNumber: certNum,
						});
					}
				} else {
					self.showLoginOrJoinForAddCoin = true;
				}
			};
			self.refreshUser = function() {
				accountService.refreshUser()
					.then(function(data) {
						$scope.user.Username = data.Username;
						$scope.user.Score = data.Score;
					});
			};
			self.updateMetadata = function(e, metaKey) {
				metadataService.updateMetadata(metaKey, Array.prototype.slice.call(arguments, 2));
			};

			$window.recaptchaReady = function() {
				$scope.$broadcast("recaptcha.ready");
			};

			// Add recaptcha script.
			if(CCG.showRecaptcha) {
				var tag		= document.createElement("script");
				tag.src		= "https://www.google.com/recaptcha/api.js?onload=recaptchaReady&render=explicit";
				tag.async	= true;
				tag.defer	= true;
				$("#scripts").append(tag);
			}

			$scope.$on("addCoin.loginOrJoin", self.openAddCoin);
			$scope.$on("user.refresh", self.refreshUser);
			$scope.$on("page.metadata.update", self.updateMetadata);
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("adminService", [
		"webServiceFactory", "Upload",
		function(webServiceFactory, Upload) {
			return webServiceFactory.create({
				importTranslations: function(importFile, importOptions) {
					var data = $.extend({ file: importFile }, importOptions);
					return Upload.upload({
						url	: "/admin/translations/import/",
						data: data
					});
				}
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("iFrameTestAdminController", [
		function() {
			var $ctrl = this;

			$ctrl.testPage = function() {
				$ctrl.error = null;

				if(!$ctrl.testPageUrl || $ctrl.testPageUrl == '') {
					$ctrl.error = "Please enter a page URL.";
					return;
				}

				$ctrl.testPageUrl = $ctrl.testPageUrl.replace("http://", "https://");
				document.getElementById('test-iframe').src = $ctrl.testPageUrl;
			};
		}
	]);

;
"use strict";
angular.module("NGC.Registry")
	.controller("coinMappingAdminController", [
		"$q", "$scope", "coinMappingAdminService", "urlService",
		function($q, $scope, coinMappingAdminService, urlService) {
			var $ctrl = this;

			$ctrl.selectedCoin        = null;
			$ctrl.mappingsToDelete    = null;
			$ctrl.indexToDelete       = null;
			$ctrl.coinsWithNoPCGSOnly = false;
			$ctrl.coinsWithNoDUIDOnly = false;
			$scope.$on("$locationChangeSuccess", loadData);

			coinMappingAdminService.fetchCountries()
				.then(function(countries) {
					$ctrl.countries = countries;
				});

			function loadData() {
				$ctrl.focusDisabled = true;

				var url           = new URI();
				var countryID     = parseInt(url.segment(2));
				var categoryID    = parseInt(url.segment(3));
				var subcategoryID = parseInt(url.segment(4));
				var params = url.search(true);
				var page = parseInt(params["page"]) || 1;

				$q.all([
					(countryID) ? coinMappingAdminService.fetchCategoriesForCountry(countryID) : null,
					(countryID && categoryID) ? coinMappingAdminService.fetchSubcategoriesForCategory(countryID, categoryID) : null,
					(countryID && categoryID && subcategoryID) ? coinMappingAdminService.fetchCoinMappingsForSubcategory(countryID, categoryID, subcategoryID, page, $ctrl.coinsWithNoPCGSOnly, $ctrl.coinsWithNoDUIDOnly) : null,
				])
					.then(function(results) {
						$ctrl.categories    = results[0];
						$ctrl.subcategories = results[1];
						$ctrl.coins         = results[2];
						$ctrl.countryID     = countryID;
						$ctrl.categoryID    = categoryID;
						$ctrl.subcategoryID = subcategoryID;
						$ctrl.currentPage   = page;

						if(!$ctrl.categoryID && $ctrl.categories && $ctrl.categories.length === 1) {
							$ctrl.categoryID = $ctrl.categories[0].NGCResourceCategoryID;
							$ctrl.categoryUpdated();
						}
					});

				urlService.replaceUrl(urlService.buildUrl(urlService.fullPath(), { 'page': page }));
			}

			$ctrl.countryUpdated = function() {
				urlService.replaceUrl(URI.expand("/admin/coin-mapping/{countryID}/", {
					countryID: $ctrl.countryID,
				}).toString());
			};
			$ctrl.categoryUpdated = function() {
				urlService.replaceUrl(URI.expand("/admin/coin-mapping/{countryID}/{categoryID}/", {
					countryID: $ctrl.countryID,
					categoryID: $ctrl.categoryID,
				}).toString());
			};
			$ctrl.subcategoryUpdated = function() {
				urlService.replaceUrl(URI.expand("/admin/coin-mapping/{countryID}/{categoryID}/{subcategoryID}/", {
					countryID: $ctrl.countryID,
					categoryID: $ctrl.categoryID,
					subcategoryID: $ctrl.subcategoryID,
				}).toString());
			};
			$ctrl.showDeletionConfirmation = function(coin, array, index) {
				$ctrl.selectedCoin = coin;
				$ctrl.mappingsToDelete = array;
				$ctrl.indexToDelete = index;
				$ctrl.showDeleteModal = true;
			};
			$ctrl.removeMapping = function() {
				$ctrl.mappingsToDelete.splice($ctrl.indexToDelete, 1);
				$ctrl.showDeleteModal = false;
				$ctrl.save($ctrl.selectedCoin);
			};
			$ctrl.addPCGSNumber = function(coin) {
				$ctrl.focusDisabled = false;
				coin.PCGSNumbers.push({
					CoinID: coin.CoinID,
					PCGSNumber: null,
					ImportSource: null,
					Verified: true
				});
			};
			$ctrl.addDUID = function(coin) {
				$ctrl.focusDisabled = false;
				coin.DUIDs.push({
					CoinID: coin.CoinID,
					DUID: null,
					OldDUID: null,
					ImportSource: null,
					Verified: true
				});
			};

			$ctrl.refresh = function(page) {
				$ctrl.focusDisabled = true;

				return coinMappingAdminService.fetchCoinMappingsForSubcategory($ctrl.countryID, $ctrl.categoryID, $ctrl.subcategoryID, page, $ctrl.coinsWithNoPCGSOnly, $ctrl.coinsWithNoDUIDOnly)
					.then(function(result) {
						$ctrl.coins = result;
						$ctrl.currentPage = page;
						urlService.replaceUrl(urlService.buildUrl(urlService.fullPath(), { 'page': page }));
					});
			};

			$ctrl.save = function(item) {
				coinMappingAdminService.saveCoinMappings(item);
			};
		}
	]);

;
"use strict";
angular.module("NGC.Registry")
	.factory("coinMappingAdminService", [
		"webServiceFactory",
		function(webServiceFactory) {
			return webServiceFactory.create({
				fetchCountries: function() {
					return this.get("/admin/coin-mapping/data/countries/");
				},
				fetchCategoriesForCountry: function(countryID) {
					return this.get("/admin/coin-mapping/data/countries/" + countryID + "/categories/");
				},
				fetchSubcategoriesForCategory: function(countryID, categoryID) {
					return this.get("/admin/coin-mapping/data/countries/" + countryID + "/categories/" + categoryID + "/subcategories/");
				},
				fetchCoinMappingsForSubcategory: function(countryID, categoryID, subcategoryID, page, filterPCGS, filterDUID) {
					page = page || 1;
					return this.get("/admin/coin-mapping/data/countries/" + countryID + "/categories/" + categoryID + "/subcategories/" + subcategoryID + "/coin-mappings/?page=" + page + "&filterPCGS=" + filterPCGS + "&filterDUID=" + filterDUID);
				},
				fetchCoinMappingsForCoin: function(coinID) {
					return this.get("/admin/coin-mapping/data/coins/" + coinID + "/coin-mappings/");
				},
				saveCoinMappings: function(coin) {
					return this.put("/admin/coin-mapping/data/coin-mappings/", coin);
				},
				getPCGSMapping: function(coinID, pcgsNumber) {
					return this.get("/admin/coin-mapping/data/coins/" + coinID + "/pcgs-mapping/", {
						pcgsNumber: pcgsNumber,
					});
				},
				deletePCGSMapping: function(coinID, pcgsNumber) {
					return this.post("/admin/coin-mapping/data/coins/" + coinID + "/delete-pcgs-mapping/", {
						pcgsNumber: pcgsNumber,
					});
				}
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("allLabelsAdminController", [
		"$scope", "labelAdminService", "Upload",
		function($scope, labelAdminService, Upload) {
			var $ctrl = this;
			labelAdminService.fetchAllLabels()
				.then(function(labels) {
					$ctrl.labels = labels || [];
					$ctrl.labels.forEach(function(label) {
						labelAdminService.fetchLabelSites(label.NGCLabelID)
							.then(function(labelSites) {
								label.sites = labelSites;
								label.isUnpublished = _.every(label.sites, { Published: false });
							});
					});
				});

			$ctrl.createNewLabel = function() {
				$ctrl.showSubmitError = false;
				$scope.form.$submitting = true;

				labelAdminService.createLabel({
						name: $ctrl.labelName,
						labelNumber: $ctrl.labelNumber,
						labelImage: $ctrl.labelImage,
					})
					.then(function(newLabel) {
						// Add the label to the label list
						newLabel.isUnpublished = true;
						$ctrl.labels.push(newLabel);
						$ctrl.labels = _.sortBy($ctrl.labels, "LabelNumber");

						// Reset the form
						$ctrl.labelName   = "";
						$ctrl.labelNumber = "";
						$ctrl.labelImage  = "";
						$scope.form.$setPristine();
						$scope.form.$setUntouched();

						// Hide the modal
						$ctrl.showCreateLabelModal = false;
					})
					.catch(function(err) {
						$ctrl.showSubmitError = true;
					})
					.finally(function() {
						$scope.form.$submitting = false;
					});
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("labelAdminService", [
		"webServiceFactory", "Upload",
		function(webServiceFactory, Upload) {
			return webServiceFactory.create({
				// Category Routes
				fetchAllCategories: function() {
					return this.get("/admin/labels/data/categories/");
				},
				fetchCategoryParentLabelList: function(categoryID) {
					return this.get("/admin/labels/data/categories/" + categoryID + "/parent-labels/");
				},
				saveCategoryParentLabelList: function(categoryID, parentLabels) {
					return this.put("/admin/labels/data/categories/" + categoryID + "/parent-labels/", parentLabels);
				},
				createParentLabel: function(categoryID, newParentLabel) {
					return this.post("/admin/labels/data/categories/" + categoryID + "/parent-labels/", newParentLabel);
				},

				// Parent Label Routes
				getParentLabel: function(parentLabelID) {
					return this.get("/admin/labels/data/parent-labels/" + parentLabelID + "/");
				},
				saveParentLabel: function(parentLabelID, parentLabel, newSampleImage, newHeaderImage) {
					var formData = _.merge({
						sampleImage: newSampleImage,
						headerImage: newHeaderImage,
					}, parentLabel);

					return Upload.upload({
						method: "PUT",
						url: "/admin/labels/data/parent-labels/" + parentLabelID + "/",
						data: _.omitBy(formData, _.isNull)
					});
				},
				deleteParentLabel: function(parentLabelID) {
					return this.post("/admin/labels/data/parent-labels/" + parentLabelID + "/delete/");
				},
				fetchParentLabelLabels: function(parentLabelID) {
					return this.get("/admin/labels/data/parent-labels/" + parentLabelID + "/labels/");
				},
				saveParentLabelLabels: function(parentLabelID, labels) {
					var sortedLabels = _.map(labels, function(label) {
						return {
							NGCLabelID: label.NGCLabelID,
							NGCLabelParentID: label.NGCLabelParentID
						}
					});
					return this.put("/admin/labels/data/parent-labels/" + parentLabelID + "/labels/", sortedLabels);
				},
				addLabelToParent: function(parentLabelID, label) {
					return this.post("/admin/labels/data/parent-labels/" + parentLabelID + "/labels/", label);
				},

				// Label Routes
				getLabel: function(labelID) {
					return this.get("/admin/labels/data/labels/" + labelID + "/");
				},
				fetchLabelSites: function(labelID, publishedOnly) {
					var promise = this.get("/admin/labels/data/labels/" + labelID + "/sites/");
					if(publishedOnly) {
						promise = promise.then(function(labelSites) {
							return _.filter(labelSites, "Published");
						});
					}

					return promise;
				},
				saveLabel: function(labelID, label, newSampleImage) {
					var formData = _.merge({
						sampleImage: newSampleImage,
					}, label);

					return Upload.upload({
						method: "PUT",
						url: "/admin/labels/data/labels/" + labelID + "/",
						data: _.omitBy(formData, _.isNull)
					});
				},
				saveLabelSites: function(labelID, labelSites) {
					return this.put("/admin/labels/data/labels/" + labelID + "/sites/", labelSites);
				},
				deleteLabel: function(labelID) {
					return this.post("/admin/labels/data/labels/" + labelID + "/delete/");
				},

				// All Label Routes
				fetchAllLabels: function() {
					return this.get("/admin/labels/data/labels/");
				},
				fetchUnassignedLabels: function() {
					return this.get("/admin/labels/data/labels/?unassignedOnly=true");
				},
				createLabel: function(newLabel) {
					return Upload.upload({
							url	: "/admin/labels/data/labels/",
							data: _.omitBy(newLabel, _.isNull)
						})
						.then(function(response) {
							return response.data;
						});
				},
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("labelCategoriesAdminController", [
		"labelAdminService",
		function(labelAdminService) {
			var self = this;
			labelAdminService.fetchAllCategories()
				.then(function(categories) {
					self.categories = categories;
				});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("labelEditAdminController", [
		"$scope", "$q", "labelAdminService",
		function($scope, $q, labelAdminService) {
			var $ctrl = this;
			var uri = new URI();
			var labelID = uri.segment(-2);

			$q.all([
				labelAdminService.getLabel(labelID),
				labelAdminService.fetchAllCategories(),
				labelAdminService.fetchLabelSites(labelID)
			])
				.then(function(results) {
					$ctrl.label = results[0] || {};
					$ctrl.label.descriptionType = !!$ctrl.label.EligibleCoins ? "eligible" : "description";
					$ctrl.categories = results[1];
					$ctrl.labelSites = results[2];

					$ctrl.selectedLabelSite = _.head($ctrl.labelSites);

					if($ctrl.label.NGCLabelParentID) {
						return labelAdminService.getParentLabel($ctrl.label.NGCLabelParentID);
					} else {
						return null;
					}
				})
				.then(function(parentLabel) {
					if(parentLabel) {
						$ctrl.categoryID = parentLabel.NGCLabelCategoryID;
						return $ctrl.changeCategory();
					}
				});

			$ctrl.changeCategory = function() {
				if(!$ctrl.categoryID) {
					$ctrl.parentLabels = [];
					return;
				}
				return labelAdminService.fetchCategoryParentLabelList($ctrl.categoryID)
					.then(function(parentLabels) {
						$ctrl.parentLabels = parentLabels;
					});
			};

			$ctrl.save = function() {
				if($ctrl.label.descriptionType === "description") {
					$ctrl.label.EligibleCoins = "";
				} else {
					$ctrl.label.Description = "";
				}

				if(!$ctrl.label.OversizeAvailable)
					$ctrl.label.OversizeLabelNumber = "";

				$q.all([
					labelAdminService.saveLabel(labelID, $ctrl.label, $ctrl.newSampleImage),
					labelAdminService.saveLabelSites(labelID, $ctrl.labelSites),
				])
					.then(function(label) {
						$ctrl.successMessage = "All changes saved.";
					});
			};

			$ctrl.delete = function() {
				labelAdminService.deleteLabel(labelID)
					.then(function() {
						window.location.href = "/admin/labels/all/";
					});
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("parentLabelEditAdminController", [
		"$q", "$scope", "labelAdminService", "dndBulkMoveService",
		function($q, $scope, labelAdminService, dndBulkMoveService) {
			var $ctrl				= this;
			var uri					= new URI();
			var categoryID			= uri.segment(-4);
			var parentLabelID		= uri.segment(-2);
			$ctrl.currentIndexes	= {};

			$q.all([
				labelAdminService.getParentLabel(parentLabelID),
				labelAdminService.fetchParentLabelLabels(parentLabelID),
				labelAdminService.fetchUnassignedLabels()
			])
				.then(function(results) {
					$ctrl.parentLabel      = results[0] || {};
					$ctrl.labels           = results[1] || [];
					$ctrl.unassignedLabels = results[2] || [];
					
					$ctrl.labels.forEach(updateLabelSites);

					for(var i = 0; i < $ctrl.labels.length; i++)
						$ctrl.currentIndexes[i] = i;
				});

			$ctrl.save = function() {
				labelAdminService.saveParentLabel(parentLabelID, $ctrl.parentLabel, $ctrl.newSampleImage, $ctrl.newHeaderImage)
					.then(showSuccessMessage);
			};

			$ctrl.addLabel = function() {
				if(!$ctrl.newLabel) {
					return;
				}

				$ctrl.newLabel.SortOrder = $ctrl.labels.length + 1;
				updateLabelSites($ctrl.newLabel);
				
				labelAdminService.addLabelToParent(parentLabelID, $ctrl.newLabel)
					.then(function() {
						$ctrl.labels.push($ctrl.newLabel);
						_.remove($ctrl.unassignedLabels, $ctrl.newLabel);
					})
					.then(showSuccessMessage);
			};

			$ctrl.delete = function() {
				labelAdminService.deleteParentLabel(parentLabelID)
					.then(function() {
						window.location.href = "/admin/labels/categories/" + categoryID + "/";
					});
			};

			$ctrl.labelsMoved = function(oldIndex) {
				// Remove the old item from the list, the new item has already been added by the dnd-draggable at this point.
				$ctrl.labels.splice(oldIndex, 1);
				for(var i = 0; i < $ctrl.labels.length; i++)
					$ctrl.currentIndexes[i] = i;
				labelAdminService.saveParentLabelLabels(parentLabelID, $ctrl.labels)
					.then($ctrl.deselectAll);
			};

			$ctrl.changeSortOrder = function(index) {
				var newIndex = $ctrl.currentIndexes[index];
				var label    = $ctrl.labels[index];
				var oldIndex = index;

				if(oldIndex === newIndex)
					return;
				else if(oldIndex > newIndex)
					oldIndex++;
				else if(oldIndex < newIndex)
					newIndex++;

				$ctrl.labels.splice(newIndex, 0, label);
				$ctrl.labelsMoved(oldIndex);
			};

			$ctrl.deselectAll = function() {
				dndBulkMoveService.deselectAll($ctrl.labels);
			};

			function showSuccessMessage() {
				$ctrl.successMessage = "All changes saved.";
			}

			function updateLabelSites(label) {
				labelAdminService.fetchLabelSites(label.NGCLabelID)
					.then(function(labelSites) {
						label.sites = labelSites;
						label.isUnpublished = _.every(label.sites, { Published: false });
					});
			}

			// Bulk Move
			$ctrl.onDrop = function(items, index) {
				dndBulkMoveService.onDrop($ctrl, $ctrl.labels, items, index);
				labelAdminService.saveParentLabelLabels(parentLabelID, $ctrl.labels);
				return true;
			};

			$ctrl.getSelectedItemsIncluding = function(item) {
				return dndBulkMoveService.getSelectedItemsIncluding($ctrl.labels, item);
			};

			$ctrl.onDragStart = function(event) {
				return dndBulkMoveService.onDragStart($ctrl, event);
			};

			$ctrl.onSelected = function(item, event) {
				return dndBulkMoveService.onSelected(item);
			};

			$ctrl.onDragEnd = function() {
				return dndBulkMoveService.onDragEnd($ctrl);
			};

			$ctrl.onCanceled = function() {
				return dndBulkMoveService.onCanceled($ctrl.labels);
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("parentLabelListAdminController", [
		"$scope", "labelAdminService", "dndBulkMoveService",
		function($scope, labelAdminService, dndBulkMoveService) {
			var $ctrl				= this;
			var uri					= new URI();
			var categoryID			= uri.segment(-2);
			$ctrl.currentIndexes	= {};

			labelAdminService.fetchCategoryParentLabelList(categoryID)
				.then(function(labels) {
					$ctrl.labels = labels || [];
					for(var i = 0; i < $ctrl.labels.length; i++)
						$ctrl.currentIndexes[i] = i;
				});

			$ctrl.save = function() {
				labelAdminService.saveCategoryParentLabelList(categoryID, $ctrl.labels)
					.then($ctrl.deselectAll);
			};

			function labelMoved(oldIndex) {
				// Remove the old item from the list, the new item has already been added by the dnd-draggable at this point.
				$ctrl.labels.splice(oldIndex, 1);
				for(var i = 0; i < $ctrl.labels.length; i++)
						$ctrl.currentIndexes[i] = i;
				$ctrl.save();
			};

			$ctrl.changeSortOrder = function(index) {
				var newIndex = $ctrl.currentIndexes[index];
				var label    = $ctrl.labels[index];
				var oldIndex = index;

				if(oldIndex === newIndex)
					return;
				else if(oldIndex > newIndex)
					oldIndex++;
				else if(oldIndex < newIndex)
					newIndex++;

				$ctrl.labels.splice(newIndex, 0, label);
				labelMoved(oldIndex);
			};

			$ctrl.createParentLabel = function() {
				var newParentLabel = {
					name: $ctrl.parentLabelName,
					sortOrder: $ctrl.labels.length + 1
				};
				labelAdminService.createParentLabel(categoryID, newParentLabel)
					.then(function(parentLabel) {
						$ctrl.labels.push(parentLabel);
						$ctrl.parentLabelName = "";
						$scope.form.$setPristine();
						$scope.form.$setUntouched();
					});
			};

			$ctrl.deselectAll = function() {
				dndBulkMoveService.deselectAll($ctrl.labels);
			};

			// Bulk Move
			$ctrl.onDrop = function(items, index) {
				dndBulkMoveService.onDrop($ctrl, $ctrl.labels, items, index);
				labelAdminService.saveCategoryParentLabelList(categoryID, $ctrl.labels);	
				return true;
			};

			$ctrl.getSelectedItemsIncluding = function(item) {
				return dndBulkMoveService.getSelectedItemsIncluding($ctrl.labels, item);
			};

			$ctrl.onDragStart = function(event) {
				return dndBulkMoveService.onDragStart($ctrl, event);
			};

			$ctrl.onSelected = function(item, event) {
				return dndBulkMoveService.onSelected(item, event);
			};

			$ctrl.onDragEnd = function() {
				return dndBulkMoveService.onDragEnd($ctrl);
			};

			$ctrl.onCanceled = function() {
				return dndBulkMoveService.onCanceled($ctrl.labels);
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("priceEditAdminController", [
		"priceGuideAdminService", "coinMappingAdminService",
		function(priceGuideAdminService, coinMappingAdminService) {
			var $ctrl = this;
			var uri = new URI();
			var coinID = uri.segment(3);
			var labelCode = uri.segment(4);
			var params = uri.search(true);
			$ctrl.requestGrade		= params["g"];
			$ctrl.errors			= [];
			$ctrl.mappingsToDelete	= null;
			$ctrl.indexToDelete     = null;

			priceGuideAdminService.fetchPrices(coinID, labelCode)
				.then(function(result) {
					$ctrl.coin = result;
				});

			coinMappingAdminService.fetchCoinMappingsForCoin(coinID)
				.then(function(result) {
					$ctrl.coinMapping = result;
				});

			$ctrl.useBCPClicked = function() {
				if($ctrl.coin.UseBaseCoinPrice) {
					$ctrl.showBaseCoinSaveModal = true;
				} else {
					$ctrl.save();
				}
			};
			$ctrl.useMeltValueClicked = function(price) {
				if(!$ctrl.coin.MeltValue)
					return;

				if(price.UseMeltValue) {
					if(!price.Premium && price.OldPremium)
						price.Premium = price.OldPremium;

					price.NGCPrice = $ctrl.coin.MeltValue.Value;
					if(price.Premium) {
						price.NGCPrice += parseFloat(price.Premium);
					}
				} else {
					if(price.Premium) {
						price.OldPremium = price.Premium;
						price.Premium = null;
					}
				}
			};
			$ctrl.saveBCP = function() {
				$ctrl.showBaseCoinSaveModal = false;
				_.each($ctrl.coin.CoinPrices, function(price) {
					price.UseMeltValue = false;
				});
				$ctrl.save();
			};
			$ctrl.cancelBCPSave = function() {
				$ctrl.showBaseCoinSaveModal			= false;
				$ctrl.coin.UseBaseCoinPrice			= false;
				$ctrl.coin.IncreaseToBaseCoinPrice	= null;
			};
			$ctrl.save = function() {
				$ctrl.errors					= {};
				$ctrl.successMessage			= "";
				$ctrl.failedMessage			    = null;
				$ctrl.isSaving					= true;
				$ctrl.coinMapping.errorMessage	= null;

				priceGuideAdminService.savePrices(coinID, labelCode, $ctrl.coin)
					.then(function(res) {
						$ctrl.coin.UseBaseCoinPrice        = res.UseBaseCoinPrice;
						$ctrl.coin.IncreaseToBaseCoinPrice = res.IncreaseToBaseCoinPrice;

						_.forEach(res.GradesToRemove, function(grade) {
							var i = _.findIndex($ctrl.coin.CoinPrices, { "Grade": grade });
							$ctrl.coin.CoinPrices[i] = {
								Grade:  grade,
								CoinID: coinID
							};
						});

						_.forEach(res.Prices, function(priceResult) {
							var i = _.findIndex($ctrl.coin.CoinPrices, { "Grade": priceResult.Price.Grade });
							$ctrl.coin.CoinPrices[i] = priceResult.Price;

							if(priceResult.SaveError !== "") {
								$ctrl.errors[priceResult.Price.Grade] = priceResult.SaveError;
							}
						});

						if(Object.keys($ctrl.errors).length > 0 || res.Error) {
							$ctrl.failedMessage = "Changes not saved, please correct errors and try again. ";
						} else {
							$ctrl.successMessage = "Changes saved.";
						}
						
						if(res.Error)
							$ctrl.failedMessage += res.Error;
					}, function() {
						$ctrl.failedMessage = "Changes not saved, please correct errors and try again.";
					});

				coinMappingAdminService.saveCoinMappings($ctrl.coinMapping)
					.then(function() {
					})
					.catch(function() {
						$ctrl.coinMapping.errorMessage = "Error saving coin mapping.";
					}).finally(function() {
						$ctrl.isSaving = false;
					});
			};
			$ctrl.showDeleteConfirmation = function(grade) {
				$ctrl.gradeToDelete = grade;
			};
			$ctrl.delete = function() {
				priceGuideAdminService.deletePrice(coinID, labelCode, $ctrl.gradeToDelete)
					.then(function(result) {
						$ctrl.coin = result;
						$ctrl.gradeToDelete = null;
					});
			};
			$ctrl.addPCGSNumber = function() {
				$ctrl.coinMapping.PCGSNumbers.push({
					CoinID: coinID,
					PCGSNumber: null,
					ImportSource: null,
					Verified: true
				});
			};
			$ctrl.addDUID = function() {
				$ctrl.coinMapping.DUIDs.push({
					CoinID: coinID,
					DUID: null,
					OldDUID: null,
					ImportSource: null,
					Verified: true
				});
			};
			$ctrl.showCoinMappingDeleteConfirmation = function(array, index) {
				$ctrl.mappingsToDelete			= array;
				$ctrl.indexToDelete				= index;
				$ctrl.showDeleteMappingModal	= true;
			};
			$ctrl.removeMapping = function() {
				$ctrl.mappingsToDelete.splice($ctrl.indexToDelete, 1);
				$ctrl.showDeleteMappingModal = false;
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("priceGuideAdminService", [
		"webServiceFactory",
		function(webServiceFactory) {
			return webServiceFactory.create({
				fetchPrices: function(coinID, labelCode) {
					var labelCodeUrl = (labelCode ? labelCode + "/" : "");
					return this.get("/admin/price-guide/data/coin-prices/" + coinID + "/" + labelCodeUrl);
				},
				savePrices: function(coinID, labelCode, coin) {
					var labelCodeUrl = (labelCode ? labelCode + "/" : "");
					return this.put("/admin/price-guide/data/coin-prices/" + coinID + "/" + labelCodeUrl, coin);
				},
				deletePrice: function(coinID, labelCode, grade) {
					var labelCodeUrl = (labelCode ? "/" + labelCode : "");
					return this.post("/admin/price-guide/data/coin-prices/" + coinID + labelCodeUrl + "/delete/", { grade: grade });
				},
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("worldPriceEditAdminController", [
		"worldPriceGuideAdminService",
		function(worldPriceGuideAdminService) {
			var $ctrl				= this;
			var uri					= new URI();
			var duid				= uri.segment(-3);
			$ctrl.duid				= duid;
			$ctrl.failed			= false;
			$ctrl.errors			= [];
			$ctrl.designation		= uri.segment(-2);
			$ctrl.refDuidToDelete	= null;
			$ctrl.touchedCoinIDs	= {};
		
			$ctrl.loadPrices = function() {
				worldPriceGuideAdminService.fetchPrices(duid, $ctrl.designation)
				.then(function(result) {
					$ctrl.coin = result;
				});
			};
			$ctrl.save = function() {
				$ctrl.failed = false;
				$ctrl.errors = [];
				var successCount = 0;

				Object.keys($ctrl.touchedCoinIDs).forEach(function(idx) {
					$ctrl.saveCoinId($ctrl.touchedCoinIDs[idx]);
				});
				$ctrl.touchedCoinIDs = {};

				worldPriceGuideAdminService.savePrices(duid, $ctrl.designation, $ctrl.coin)
					.then(function(results) {
						_.forEach(results.CoinPriceSaveResults, function(result) {
							if(result.SaveError !== "") {
								$ctrl.errors[result.Price.Grade] = result.SaveError;
							}
							if(result.SaveSuccess) {
								successCount++;
							}
						});

						if(Object.keys($ctrl.errors).length > 0) {
							$ctrl.failed = true;
						} else if(successCount > 0 || results.MintageSaveSuccess || results.YearOnCoinSaveSuccess) {
							$ctrl.successMessage = "Changes saved.";
						}
					});
			};
			$ctrl.showDeleteConfirmation = function(grade) {
				$ctrl.gradeToDelete = grade;
			};
			$ctrl.delete = function() {
				worldPriceGuideAdminService.deletePrice(duid,  $ctrl.designation, $ctrl.gradeToDelete)
					.then(function(result) {
						$ctrl.coin = result;
						$ctrl.gradeToDelete = null;
					});
			};
			$ctrl.saveCoinId = function(refDuid) {
				refDuid.errorMessage = null;
				// don't send the request if nothing is changed.
				if(refDuid.OldCoinID === refDuid.CoinID
					&& refDuid.WasVerified === refDuid.Verified)
					return;
					
				// if a user deletes by deleting the coin id, don't update, delete.
				if(refDuid.CoinID) {
					worldPriceGuideAdminService.saveCoinID(refDuid.DUID, refDuid)
						.then(function(result) {
							if(!result.SaveSuccess) {
								refDuid.errorMessage = "Error saving coin mapping.";
							} else {
								refDuid.CoinReferenceLink	= result.CoinReferenceLink;
								refDuid.CoinDescription		= result.CoinDescription;
								refDuid.ModifiedNote		= result.ModifiedNote;
								refDuid.OldCoinID			= result.CoinID;
								refDuid.WasVerified			= result.Verified;	
							}
						});
				} else {
					$ctrl.deleteCoinId(refDuid);
				}
			};
			$ctrl.showDeleteRefDuidModal = function(index) {
				var refDuid = $ctrl.coin.CoinReferenceDuids[index];
				if(refDuid.CoinID || refDuid.OldCoinID) {
					$ctrl.refDuidToDelete			= refDuid;
					$ctrl.showRefDuidDeleteModal	= true;
				} else {
					$ctrl.coin.CoinReferenceDuids.splice(index, 1);
				}
				
			};
			$ctrl.deleteCoinId = function() {
				var refDuid = $ctrl.refDuidToDelete;
				$ctrl.refDuidToDelete			= null;
				$ctrl.showRefDuidDeleteModal	= false;
				// if you don't have a coinId at all, do nothing
				if(!refDuid || !refDuid.OldCoinID)
					return;

				refDuid.errorMessage = null;
				worldPriceGuideAdminService.deleteCoinID(refDuid.DUID, refDuid.OldCoinID)
					.then(function(result) {
						if(!result.SaveSuccess) {
							refDuid.errorMessage = "Error deleting coin mapping.";
						}
						else {
							refDuid.HideDelete = true;
							$ctrl.loadPrices();
						}
					});
			};
			$ctrl.addRefDuidRow = function() {
				$ctrl.coin.CoinReferenceDuids.push({ DUID: duid });
			};

			$ctrl.loadPrices();
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("worldPriceGuideAdminService", [
		"webServiceFactory",
		function(webServiceFactory) {
			return webServiceFactory.create({
				fetchPrices: function(duid, designation) {
					return this.get("/admin/world-price-guide/data/coin-prices/" + duid + "/" + designation + "/");
				},
				savePrices: function(duid, designation, coin) {
					return this.put("/admin/world-price-guide/data/coin-prices/" + duid + "/", {
						designation: designation,
						coin       : coin,
					});
				},
				deletePrice: function(duid, designation, grade) {
					return this.post("/admin/world-price-guide/data/coin-prices/" + duid + "/delete/", { 
						designation : designation,
						grade: grade 
				});
				},
				saveCoinID: function(duid, refDuid) {
					return this.put("/admin/world-price-guide/data/coin-mappings/" + duid + "/", {
						oldCoinId: refDuid.OldCoinID,
						newCoinId: refDuid.CoinID,
						verified:  refDuid.Verified
					});
				},
				deleteCoinID: function(duid, coinId) {
					return this.post("/admin/world-price-guide/data/coin-mappings/" + duid + "/delete/", {
						coinId: coinId,
					});
				},
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("pcgsToNGCLookupAdminController", [
		"$scope", "registryAdminService",
		function($scope, registryAdminService) {
			var $ctrl = this;
			$ctrl.matchedNGCCoins = null;

			$ctrl.searchMatchedNGCCoins = function() {
				$ctrl.matchedNGCCoins = null;
				$ctrl.isSubmitting = true;

				registryAdminService.lookupNGCCoinNumberForPCGSNumber($ctrl.pcgsNumber)
					.then(function(result) {
						$ctrl.matchedNGCCoins = result;
						$ctrl.isSubmitting = false;
					});
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("registryAdminService", [
		"webServiceFactory", "Upload",
		function(webServiceFactory, Upload) {
			return webServiceFactory.create({
				getCoinDescription: function(coinID) {
					return this.get("/admin/registry/data/coin/" + coinID + "/coin-description/");
				},
				updateCoinIDForPeopleCoin: function(peopleCoinID, newCoinID) {
					return this.post("/admin/registry/data/people-coin/" + peopleCoinID + "/update-coin-id/", {
						newCoinID: newCoinID
					});
				},
				updateCoinGradeForPeopleCoin: function(peopleCoinID, newGrade) {
					return this.post("/admin/registry/data/people-coin/" + peopleCoinID + "/update-grade/", {
						newGrade: newGrade
					});
				},
				fetchEligibleCoinsForSlot: function(slotID) {
					return this.get("/admin/registry/data/slot/" + slotID + "/eligible-coins/");
				},
				searchCoinsInRegistryByCertNum: function(certNum) {
					return this.get("/admin/registry/data/coins/" + certNum + "/");
				},
				lookupNGCCoinNumberForPCGSNumber: function(pcgsNumber) {
					return this.get("/admin/registry/data/pcgs-to-ngc-lookup/" + pcgsNumber +"/");
				}
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("searchCoinsAdminController", [
		"registryAdminService",
		function(registryAdminService) {
			var $ctrl = this;

			$ctrl.searchCoins = function() {
				$ctrl.noCoinFound = false;
				$ctrl.coinsFound = null;

				registryAdminService.searchCoinsInRegistryByCertNum($ctrl.certNumber)
					.then(function(result) {
						if(!result || result.length == 0)
							$ctrl.noCoinFound = true;
						else if(result.length == 1)
							window.location = "/registry/coins/" + result[0].PeopleCoinID + "/";
						else
							$ctrl.coinsFound = result;
					})
			};
		}
	]);

;
"use strict";
angular.module("NGC.Registry")
	.controller("pendingCoinReviewAdminController", [
		"$attrs", "$window", "registryAdminService", "pendingCoinsAdminService", "coinMappingAdminService", "coinGrades",
		function($attrs, $window, registryAdminService, pendingCoinsAdminService, coinMappingAdminService, coinGrades) {
			var params = (new URI()).search(true);
			var fromProfileID = parseInt(params["fromProfileID"]) || 0;
			var landingPageUrl = fromProfileID > 0 ? "/profile/" + fromProfileID + "/coins/pending/" : "/admin/registry/pending-coins/";
			var reviewNextUrl  = fromProfileID > 0 ? "/profile/" + fromProfileID + "/coins/pending/" : "/admin/registry/pending-coins/review/"

			var $ctrl = this;
			$ctrl.rejectReason  = null;
			$ctrl.approveError  = null;
			$ctrl.pcgsGrade     = null;
			$ctrl.isLoaded      = false;
			$ctrl.rejectFailed  = false;
			$ctrl.pendingCoinID = $attrs.pendingCoinId;
			var coinGrades      = coinGrades["pcgs"].slice(0);
			$ctrl.pcgsGradeList = _.reverse(coinGrades);
			pendingCoinsAdminService.getPendingCoin($ctrl.pendingCoinID)
				.then(function(result) {
					if(!result) {
						$window.location.href = landingPageUrl;
					} else {
						$ctrl.pendingCoin = result;
						$ctrl.pcgsGrade = $ctrl.pendingCoin.Grade;
						if($ctrl.pendingCoin.Status > 0 && fromProfileID === 0)
							reviewNextUrl = "/admin/registry/pending-coins/list/" + $ctrl.pendingCoin.Status + "/";
						if($ctrl.pendingCoin.SlotID || $ctrl.pendingCoin.PCGSNumber) {
							$ctrl.fetchSuggestedCoins();
						}
					}

					return $ctrl.updateCoinDescription();
				})
				.finally(function() {
					$ctrl.isLoaded = true;
				});

			$ctrl.fetchSuggestedCoins = function() {
				pendingCoinsAdminService.fetchSuggestedCoinsForPendingCoin($ctrl.pendingCoin.SlotID, $ctrl.pendingCoin.PCGSNumber)
					.then(function(result) {
						$ctrl.suggestedCoins = result;
					});
			};

			$ctrl.openConfirmDeletePCGSMappingModal = function(coinID, pcgsNumber) {
				$ctrl.pcgsMappingToDelete = null;
				coinMappingAdminService.getPCGSMapping(coinID, pcgsNumber)
					.then(function(result) {
						$ctrl.pcgsMappingToDelete = result;
						$ctrl.showConfirmDeletePCGSMapping = true;
						$ctrl.deletePCGSMappingFailed = false;
					});
			}

			$ctrl.deletePCGSMapping = function() {
				if(!$ctrl.pcgsMappingToDelete) {
					$ctrl.showConfirmDeletePCGSMapping = false;
					return;
				}
				coinMappingAdminService.deletePCGSMapping($ctrl.pcgsMappingToDelete.CoinID, $ctrl.pcgsMappingToDelete.PCGSNumber)
					.then(function(success) {
						if(success) {
							$ctrl.fetchSuggestedCoins();
							$ctrl.showConfirmDeletePCGSMapping = false;
						}
						else
							$ctrl.deletePCGSMappingFailed = true;
					});
			};

			$ctrl.updateCoinID = function(coinID) {
				$ctrl.pendingCoin.CoinID = coinID;
				$ctrl.updateCoinDescription();
			};

			$ctrl.updateCoinDescription = function() {
				if(!$ctrl.pendingCoin.CoinID) {
					$ctrl.CoinDescription = "";
					$ctrl.IsCoinIDValid = true;
					return;
				}

				registryAdminService.getCoinDescription(_.get($ctrl.pendingCoin, "CoinID"))
					.then(function(result) {
						$ctrl.CoinDescription = result.CoinDescription;
						$ctrl.IsUSCoin        = result.IsUSCoin;
						$ctrl.IsCoinIDValid   = result.IsCoinIDValid;
					})
					.catch(function() {
						$ctrl.CoinDescription = "Invalid Coin ID";
						$ctrl.IsUSCoin        = false;
						$ctrl.IsCoinIDValid   = false;
					});
			};

			$ctrl.openRejectModal = function() {
				pendingCoinsAdminService.getPendingCoin($ctrl.pendingCoinID)
					.then(function(result) {
						if(!result) {
							$window.location.href = reviewNextUrl;
						} else {
							$ctrl.showConfirmRejectModal = true;
						}
					});
			};

			$ctrl.approveCoin = function() {
				pendingCoinsAdminService.getPendingCoin($ctrl.pendingCoinID)
					.then(function(result) {
						if(!result) {
							$window.location.href = reviewNextUrl;
						} else {
							$ctrl.pendingCoin.Grade = $ctrl.pcgsGrade;

							pendingCoinsAdminService.approveCoin($ctrl.pendingCoin)
								.then(function(result) {
									if(result.Success)
										$window.location.href = reviewNextUrl;
									else $ctrl.approveError = result.ErrorMessage;
								});
						}
					});
			};

			$ctrl.rejectCoin = function() {
				pendingCoinsAdminService.rejectCoin($ctrl.pendingCoinID, $ctrl.rejectReason)
					.then(function(result) {
						if(result.Success) {
							$window.location.href = reviewNextUrl;
						} else {
							$ctrl.rejectError = result.ErrorMessage;
						}
					});
			};

			$ctrl.addCoinToNeedsResearchQueue = function() {
				pendingCoinsAdminService.getPendingCoin($ctrl.pendingCoinID)
					.then(function(result) {
						if(!result) {
							$window.location.href = reviewNextUrl;
						} else {
							$ctrl.pendingCoin.Grade = $ctrl.pcgsGrade;

							pendingCoinsAdminService.addCoinToNeedsResearchQueue($ctrl.pendingCoin)
								.then(function(result) {
									if(result.Success)
										$window.location.href = reviewNextUrl;
									else $ctrl.approveError = result.ErrorMessage;
								});
						}
					});
			};

			$ctrl.addCoinToNeedsNewCoinIDQueue = function() {
				pendingCoinsAdminService.getPendingCoin($ctrl.pendingCoinID)
					.then(function(result) {
						if(!result) {
							$window.location.href = reviewNextUrl;
						} else {
							$ctrl.pendingCoin.Grade = $ctrl.pcgsGrade;

							pendingCoinsAdminService.addCoinToNeedsNewCoinIDQueue($ctrl.pendingCoin)
								.then(function(result) {
									if(result.Success)
										$window.location.href = reviewNextUrl;
									else $ctrl.approveError = result.ErrorMessage;
								});
						}
					});
			};

			$ctrl.adminResponseOptionChanged = function() {
				if($ctrl.pendingCoin.AdminResponseOption !== 'Other')
					$ctrl.pendingCoin.AdminResponseOther = null;
			};
		}
	]);

;
"use strict";
angular.module("NGC.Registry")
	.factory("pendingCoinsAdminService", [
		"webServiceFactory",
		function(webServiceFactory) {
			return webServiceFactory.create({
				getPendingCoin: function(pendingCoinID) {
					return this.get("/admin/registry/pending-coins/data/pending-coin/" + pendingCoinID + "/");
				},
				approveCoin: function(pendingCoin) {
					return this.post("/admin/registry/pending-coins/data/pending-coin/" + pendingCoin.PendingCoinID + "/approve/", {
						pcgsNumber:          pendingCoin.PCGSNumber,
						coinID:              pendingCoin.CoinID,
						grade:               pendingCoin.Grade,
						adminResponseOption: pendingCoin.AdminResponseOption,
						adminResponseOther: pendingCoin.AdminResponseOther,
					});
				},
				rejectCoin: function(pendingCoinID, rejectReason) {
					return this.post("/admin/registry/pending-coins/data/pending-coin/" + pendingCoinID + "/reject/", {
						rejectReason: rejectReason
					});
				},
				addCoinToNeedsResearchQueue: function(pendingCoin) {
					return this.post("/admin/registry/pending-coins/data/pending-coin/" + pendingCoin.PendingCoinID + "/needs-research/", {
						pcgsNumber: pendingCoin.PCGSNumber,
						coinID:     pendingCoin.CoinID,
						grade:      pendingCoin.Grade
					});
				},
				addCoinToNeedsNewCoinIDQueue: function(pendingCoin) {
					return this.post("/admin/registry/pending-coins/data/pending-coin/" + pendingCoin.PendingCoinID + "/needs-new-coin-id/", {
						pcgsNumber: pendingCoin.PCGSNumber,
						grade:      pendingCoin.Grade
					});
				},
				fetchSuggestedCoinsForPendingCoin: function(slotID, pcgsNumber) {
					var url = "/admin/registry/pending-coins/data/suggested-coins/"
					url += "?" + URI.buildQuery({
						slotID: slotID,
						pcgsNumber:  pcgsNumber,
					});
					return this.get(url);
				}
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("awardAdminController", [
		"awardAdminService", "$window",
		function(awardAdminService, $window) {
			var $ctrl = this;
			$ctrl.awardYear = {};
			$ctrl.deleteAchievementWinnerError = null;

			awardAdminService.getCurrentAwardYear()
				.then(function(awardYear) {
					if(awardYear) {
						$ctrl.awardYear = awardYear;
						getAwardNominationData($ctrl.awardYear.AwardYearID);
					} else {
						$ctrl.awardYear = null;
					}
				});

			function getAwardNominationData(awardYearID) {
				awardAdminService.fetchAwardNominationSummaries($ctrl.awardYear.AwardYearID)
					.then(function(summaries) {
						$ctrl.nominationSummaries = summaries;
					});
			}

			function resetForm(form) {
				form.$setPristine();
				form.$setUntouched();
			}

			$ctrl.openAddAchievementWinnerModal = function(awardTypeID) {
				$ctrl.showAddAchievementWinnerModal = true;
				$ctrl.awardTypeID = awardTypeID;
			};

			$ctrl.clearCutoffDateForm = function(form) {
				resetForm(form);
				$ctrl.saveTargetCutoffDateError = null;
				$ctrl.momentDate = null;
			};

			$ctrl.clearAchievementForm = function(form) {
				resetForm(form);
				$ctrl.addAchievementWinnerError = null;
				$ctrl.achievementWinnerPeopleID = null;
			};

			$ctrl.saveTargetCutoffDate = function() {
				awardAdminService.saveDateForAwardYear($ctrl.awardYear.AwardYearID, $ctrl.dateFormatted, "TargetCutoff")
					.then(function(result) {
						if(result.Success){
							$ctrl.successMessage = "Changes saved.";
							$ctrl.awardYear = result.AwardYear;
							$ctrl.showEditCutoffTimeModal = false;
						} else {
							$ctrl.saveTargetCutoffDateError = result.Error;
						}
					});
			};

			$ctrl.addAchievementWinner = function() {
				awardAdminService.addAwardWinner($ctrl.awardYear.AwardYearID, $ctrl.achievementWinnerPeopleID, $ctrl.awardTypeID)
					.then(function(result) {
						if(result.Success){
							$ctrl.successMessage = "Changes saved.";
							$ctrl.showAddAchievementWinnerModal = false;
							getAwardNominationData();
						} else {
							$ctrl.addAchievementWinnerError = result.Error;
						}
					});
			};

			$ctrl.deleteAchievementWinner = function(peopleID, awardTypeID) {
				awardAdminService.deleteAwardWinner($ctrl.awardYear.AwardYearID, peopleID, awardTypeID)
					.then(function(result) {
						if(result.Success)
							getAwardNominationData();
						else
							$ctrl.deleteAchievementWinnerError = result.Error;
					});
			};

			$ctrl.startJudging = function(form) {
				form.$submitting = true;
				awardAdminService.startJudging($ctrl.awardYear.AwardYearID)
					.then(function(result) {
						if(result.Success)
							$ctrl.startJudgingSuccess = true;
						else $ctrl.startJudgingError = result.Error;
					})
					.finally(function() {
						form.$submitting = false;
					});
			};

			$ctrl.goToJudging = function() {
				$window.location.href = "/admin/registry-awards/judging/";
			};
		}
	]);

;
"use strict";
angular.module("NGC.Registry")
	.factory("awardAdminService", [
		"webServiceFactory",
		function(webServiceFactory) {
			return webServiceFactory.create({
				getCurrentAwardYear: function() {
					return this.get("/admin/registry-awards/data/award-year/");
				},
				fetchAwardNominationSummaries: function(awardYearID) {
					return this.get("/admin/registry-awards/data/award-year/" + awardYearID + "/nomination-summaries/");
				},
				fetchAwardTypes: function() {
					return this.get("/admin/registry-awards/data/award-types/");
				},
				fetchAwardSets: function(awardYearID, awardTypeID) {
					return this.get("/admin/registry-awards/data/award-year/" + awardYearID + "/award-sets/", {
						awardTypeID: awardTypeID
					});
				},
				fetchOverallAchievementWinners: function(awardYearID) {
					return this.get("/admin/registry-awards/data/award-year/" + awardYearID + "/achievement-winners/");
				},
				saveDateForAwardYear: function(awardYearID, dateStr, dateType) {
					return this.post("/admin/registry-awards/data/award-year/" + awardYearID + "/", {
						dateStr: dateStr,
						dateType: dateType
					});
				},
				addAwardWinner: function(awardYearID, peopleID, awardTypeID) {
					return this.post("/admin/registry-awards/data/award-year/" + awardYearID + "/add-winner/", {
						peopleID: peopleID,
						awardTypeID: awardTypeID
					});
				},
				deleteAwardWinner: function(awardYearID, peopleID, awardTypeID) {
					return this.post("/admin/registry-awards/data/award-year/" + awardYearID + "/delete-winner/", {
						peopleID: peopleID,
						awardTypeID: awardTypeID
					});
				},
				startJudging: function(awardYearID) {
					return this.post("/admin/registry-awards/data/award-year/" + awardYearID + "/start-judging/");
				},
				saveJudgesComments: function(awardSetID, judgesComments) {
					return this.post("/admin/registry-awards/data/award-set/" + awardSetID + "/", {
						judgesComments: judgesComments
					});
				},
				deleteAwardSet: function(awardSetID) {
					return this.post("/admin/registry-awards/data/award-set/" + awardSetID + "/delete/");
				},
				finalizeAwards: function(awardYearID) {
					return this.post("/admin/registry-awards/data/award-year/" + awardYearID + "/finalize/");
				},
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("awardJudgingController", [
		"$element", "$scope", "$window", "awardAdminService", "awardJudgingColumns", "uiGridConstants", 
		function($element, $scope, $window, awardAdminService, awardJudgingColumns, uiGridConstants) {
			var $ctrl = this;
			$ctrl.awardYear = {};

			$ctrl.gridOptions = {
				minRowsToShow: 10,
				excessRows: 500,
				paginationPageSize: 50,
				enableColumnMenus: false,
				enableGridMenu: false,
				enableHorizontalScrollbar: uiGridConstants.scrollbars.NEVER,
				enableVerticalScrollbar: uiGridConstants.scrollbars.NEVER,
				columnDefs: awardJudgingColumns,
				onRegisterApi: function(gridApi) {
					$ctrl.gridApi = gridApi;
				}
			};

			awardAdminService.getCurrentAwardYear()
				.then(function(awardYear) {
					if(awardYear) {
						$ctrl.awardYear = awardYear;
						awardAdminService.fetchAwardTypes()
							.then(function(awardTypes){
								if(awardTypes.length > 0) {
									$ctrl.awardTypes = awardTypes;
									$ctrl.awardTypeID = awardTypes[0].AwardTypeID;
									loadSets($ctrl.awardTypeID);
								}
							});

					} else {
						$ctrl.awardYear = null;
					}
				});

			function resetForm(form) {
				form.$setPristine();
				form.$setUntouched();
			}
			
			function loadSets(awardTypeID) {
				$ctrl.isLoading = true;
				awardAdminService.fetchAwardSets($ctrl.awardYear.AwardYearID, awardTypeID)
					.then(function(results) {
						$ctrl.gridOptions.enablePagination = false;
						$ctrl.gridOptions.enablePaginationControls = $ctrl.gridOptions.enablePagination;
						$ctrl.gridOptions.data = results.awardSets;
						$ctrl.hasAwardSets = results.awardSets.length > 0;
						$ctrl.awardTypeTargetWinnerCount = results.awardTypeTargetWinnerCount;
						$ctrl.isLoading = false;
					})
			}
			
			$ctrl.clearEditJudgesCommentsForm = function(form) {
				resetForm(form);
				$ctrl.judgesCommentsInModal = null;
			};

			$ctrl.clearTargetFinalizedDateForm = function(form) {
				resetForm(form);
				$ctrl.saveTargetFinalizedDateError = null;
				$ctrl.momentDate = null;
			};

			$ctrl.updateFilter = function() {
				loadSets($ctrl.awardTypeID);
			};

			$element.on("click", ".more-nomination-comments", function(e) {
				var comments = $(e.target).attr("nomination-comments");
				var awardSetName = $(e.target).attr("award-set-name");

				$scope.$apply(function() {
					$ctrl.nominationCommentsInModal = comments;
					$ctrl.nominationCommentsAwardSetName = awardSetName;
					$ctrl.showNominationCommentsModal = true;
				});
			});

			$element.on("click", ".edit-judges-comments", function(e) {
				var comments = $(e.target).attr("judges-comments");
				var awardSetID = $(e.target).attr("award-set-id");
				var awardSetName = $(e.target).attr("award-set-name");

				$scope.$apply(function() {
					$ctrl.judgesCommentsInModal = comments;
					$ctrl.judgesCommentsAwardSetID = awardSetID;
					$ctrl.judgesCommentsAwardSetName = awardSetName;
					$ctrl.showEditJudgesCommentsModal = true;
				});
			});

			$element.on("click", ".remove-set", function(e) {
				var awardSetID = $(e.target).attr("award-set-id");
				var awardSetName = $(e.target).attr("award-set-name");

				$scope.$apply(function() {
					$ctrl.awardSetIDToRemove = parseInt(awardSetID);
					$ctrl.awardSetNameToRemove = awardSetName;
					$ctrl.showRemoveSetModal = true;
				});
			});

			$ctrl.saveTargetFinalizedDate = function() {
				awardAdminService.saveDateForAwardYear($ctrl.awardYear.AwardYearID, $ctrl.dateFormatted, "TargetFinalized")
					.then(function(result) {
						if(result.Success){
							$ctrl.successMessage = "Changes saved.";
							$ctrl.awardYear = result.AwardYear;
							$ctrl.showTargetFinalizedDateModal = false;
						} else {
							$ctrl.saveTargetFinalizedDateError = result.Error;
						}
					});
			};

			$ctrl.removeAwardSet = function() {
				awardAdminService.deleteAwardSet($ctrl.awardSetIDToRemove)
					.then(function(result) {
						if(result.Success){
							var i = _.findIndex($ctrl.gridOptions.data, { AwardSetID: parseInt($ctrl.awardSetIDToRemove) });
							$ctrl.gridOptions.data.splice(i, 1);
							$ctrl.showRemoveSetModal = false;
						} else {
							$ctrl.RemoveSetError = result.Error;
						}
					});
			};

			$ctrl.saveJudgesComments = function() {
				awardAdminService.saveJudgesComments($ctrl.judgesCommentsAwardSetID, $ctrl.judgesCommentsInModal)
					.then(function(result) {
						if(result.Success){
							var i = _.findIndex($ctrl.gridOptions.data, { AwardSetID: parseInt($ctrl.judgesCommentsAwardSetID) });
							$ctrl.gridOptions.data[i].JudgesComments = $ctrl.judgesCommentsInModal;
							$ctrl.successMessage = "Changes saved.";
							$ctrl.showEditJudgesCommentsModal = false;
						} else {
							$ctrl.saveJudgesCommentsError = result.Error;
						}
					});
			};

			$ctrl.showNominationCommentsInModal = function(comments) {
				$ctrl.nominationCommentsInModal = comments;
			};

			$ctrl.showOverallAchievementWinners = function() {
				$ctrl.showOverallAchievementModal = true;
				awardAdminService.fetchOverallAchievementWinners($ctrl.awardYear.AwardYearID)
					.then(function(winners) {
						if(winners.length > 0){
							$ctrl.overallAchievementWinners = winners;
						} else {
							$ctrl.noAchievementWinner = true;
						}
					});
			};
			$ctrl.finalizeAwards = function(form) {
				form.$submitting = true;
				awardAdminService.finalizeAwards($ctrl.awardYear.AwardYearID)
					.then(function(result) {
						if(result.Success){
							form.$submitting = false;
							$ctrl.showFinalizeModal = false;
							$window.location.href = "/admin/registry-awards/";
						} else {
							$ctrl.finalizeError = result.Error;
						}
					});
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("varietyPlusCategoriesAdminController", [
		"$q", "varietyPlusAdminService",
		function($q, varietyPlusAdminService) {
			var self = this;
			var uri = new URI();
			var countryID = uri.segment(-2);

			$q.all([
				varietyPlusAdminService.getCountry(countryID),
				varietyPlusAdminService.fetchCategoriesForCountry(countryID)
			])
				.then(function(results) {
					self.country = results[0];
					self.categories = results[1];
				});

			self.toggle = function(category) {
				if(!category.subcategories && !category.visible) {
					varietyPlusAdminService.fetchSubcategoriesForCategory(category.NGCResourceCategoryID)
						.then(function(subcategories) {
							category.subcategories = subcategories;
						});
				}

				category.visible = !category.visible;
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("varietyPlusCoinDetailAdminController", [
		"$q", "varietyPlusAdminService",
		function($q, varietyPlusAdminService) {
			var $ctrl = this;
			var uri = new URI();

			$ctrl.subcategoryID = uri.segment(-4);
			$ctrl.coinID = uri.segment(-2);
			$ctrl.varietyPlusDetails = {};
			$ctrl.varietyPlusImagesList = [];
			$ctrl.crossReferencesList = [];
			$ctrl.imageToDelete = null;

			$q.all([
				varietyPlusAdminService.getVarietyDetailsForCoin($ctrl.coinID),
				varietyPlusAdminService.fetchVarietyImagesForCoin($ctrl.coinID),
				varietyPlusAdminService.fetchCrossReferencesForSubcategory($ctrl.subcategoryID),
				varietyPlusAdminService.fetchCrossReferencesForCoin($ctrl.coinID),
			]).then(function(results) {
				$ctrl.varietyPlusDetails = results[0];

				// Ensure there are 6 images
				$ctrl.varietyPlusImagesList = _.map(_.range(1, 7), function(index) {
					return _.extend({ SortOrder: index }, _.find(results[1], { SortOrder: index }));
				});

				// Take the list of cross references for the group, and extend that with any data from this variety.
				$ctrl.crossReferencesList = _.map(results[2], function(crossReference) {
					return _.extend(crossReference, _.find(results[3], { VarietyPlusCrossReferenceID: crossReference.VarietyPlusCrossReferenceID }));
				});
			});

			$ctrl.save = function() {
				$q.all([
					varietyPlusAdminService.saveVarietyPlusDetails($ctrl.coinID, $ctrl.varietyPlusDetails),
					varietyPlusAdminService.saveVarietyPlusDetailCrossReferences($ctrl.coinID, $ctrl.crossReferencesList),
				]).then(function() {
					$ctrl.successMessage = "All changes saved.";
				});
			};

			$ctrl.showImageDeletionConfirmation = function(image) {
				$ctrl.imageToDelete = image;
				$ctrl.showImageDeleteModal = true;
			};
			$ctrl.saveImage = function(image) {
				if(!image.newImage)
					return;
				$ctrl.isSaving = true;
				varietyPlusAdminService.saveVarietyPlusImage($ctrl.coinID, image.SortOrder, image.newImage)
					.then(function(result) {
						var savedImage = result.data;
						image.newImage = null;
						_.merge($ctrl.varietyPlusImagesList[savedImage.SortOrder - 1], savedImage);
						$ctrl.isSaving = false;
					});
			};
			$ctrl.deleteImage = function() {
				if(!$ctrl.imageToDelete || !$ctrl.imageToDelete.VarietyPlusImageID)
					return;

				varietyPlusAdminService.deleteVarietyPlusImage($ctrl.coinID, $ctrl.imageToDelete.VarietyPlusImageID)
					.then(function() {
						$ctrl.imageToDelete.VarietyPlusImageID = null;
						$ctrl.imageToDelete.ThumbnailPath = null;
						$ctrl.imageToDelete.newImage = null;

						$ctrl.showImageDeleteModal = false;
					});
			};
			$ctrl.imageMoved = function(oldIndex) {
				// Remove the old item from the list, the new item has already been added by the dnd-draggable at this point.
				$ctrl.varietyPlusImagesList.splice(oldIndex, 1);
				varietyPlusAdminService.updateVarietyPlusImages($ctrl.coinID, $ctrl.varietyPlusImagesList)
					.then(function() {
						$ctrl.varietyPlusImagesList.forEach(function(item, index) {
							item.SortOrder = index + 1;
						});
					});
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("varietyPlusCountriesAdminController", [
		 "varietyPlusAdminService",
		function(varietyPlusAdminService) {
			var self = this;
			varietyPlusAdminService.fetchAllCountries()
				.then(function(countries) {
					self.countries = countries;
				});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("varietyPlusCrossReferencesAdminController", [
		"$q", "$scope", "varietyPlusAdminService",
		function($q, $scope, varietyPlusAdminService) {
			var $ctrl = this;

			$ctrl.refresh = function() {
				return varietyPlusAdminService.fetchAllCrossReferences()
					.then(function(crossReferences) {
						$ctrl.crossReferences = crossReferences || [];
					});
			};

			$ctrl.createCrossReference = function() {
				$ctrl.resetEditForm();
				$ctrl.modalAction = "Create";
				$ctrl.showEditModal = true;
			};
			$ctrl.editCrossReference = function(crossReference) {
				$ctrl.modalAction = "Edit";
				$ctrl.selectedCrossReference = crossReference;
				$ctrl.newCrossReferenceName = crossReference.Name;
				$ctrl.showEditModal = true;
			};
			$ctrl.showRemovalConfirmation = function(crossReference) {
				varietyPlusAdminService.fetchSubcategoriesForCrossReference(crossReference.VarietyPlusCrossReferenceID)
					.then(function(linkedSubcats) {
						$ctrl.linkedSubcats = linkedSubcats;
						$ctrl.selectedCrossReference = crossReference;
						$ctrl.allowDelete = ($ctrl.linkedSubcats.length === 0);
						$ctrl.showDeleteModal = true;
					});
			};

			$ctrl.save = function() {
				var savePromise = $q.when();

				if($ctrl.modalAction == "Edit") {
					$ctrl.selectedCrossReference.Name = $ctrl.newCrossReferenceName;
					savePromise = varietyPlusAdminService.saveCrossReference($ctrl.selectedCrossReference.VarietyPlusCrossReferenceID, $ctrl.selectedCrossReference);
				} else {
					savePromise = varietyPlusAdminService.createCrossReference($ctrl.newCrossReferenceName);
				}

				savePromise
					.then($ctrl.refresh)
					.then($ctrl.resetEditForm);
			};
			$ctrl.confirmRemoval = function() {
				varietyPlusAdminService.deleteCrossReference($ctrl.selectedCrossReference.VarietyPlusCrossReferenceID)
					.then(function() {
						$ctrl.crossReferences = _.without($ctrl.crossReferences, $ctrl.selectedCrossReference);
						$ctrl.showDeleteModal = false;
					});
			};

			$ctrl.resetEditForm = function() {
				$scope.form.$setPristine();
				$scope.form.$setUntouched();
				$ctrl.showEditModal = false;
				$ctrl.newCrossReferenceName = undefined;
				$ctrl.selectedCrossReference = undefined;
			};

			$ctrl.refresh();
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("varietyPlusSubcategoryAdminController", [
		"$q", "varietyPlusAdminService",
		function($q, varietyPlusAdminService) {
			var $ctrl = this;
			var uri = new URI();
			$ctrl.subcategoryID = uri.segment(-2);
			$ctrl.ccgResourceCountryID = uri.segment(-4);
			$ctrl.showHiddenVarieties = false;

			$ctrl.loadVarieties = function(page) {
				$ctrl.page = page;
				varietyPlusAdminService.fetchVarietiesForSubcategory($ctrl.subcategoryID, $ctrl.page, $ctrl.showHiddenVarieties)
					.then(function(varieties) {
						$ctrl.varieties = varieties;
					});
			};
			$ctrl.save = function() {
				varietyPlusAdminService.saveSubcategory($ctrl.subcategoryID, $ctrl.subcategory)
					.then(function() {
						$ctrl.successMessage = "All changes saved.";
					});
			};
			$ctrl.addCrossReference = function() {
				if(!$ctrl.selectedCrossReference)
					return;

				_.remove($ctrl.availableCrossReferences, { VarietyPlusCrossReferenceID: $ctrl.selectedCrossReference.VarietyPlusCrossReferenceID });
				$ctrl.crossReferences.push($ctrl.selectedCrossReference);
				$ctrl.selectedCrossReference = null;

				varietyPlusAdminService.saveSubcategoryCrossReferences($ctrl.subcategoryID, $ctrl.crossReferences);
			};
			$ctrl.showRemovalConfirmation = function(crossReference) {
				$ctrl.showRemoveModal = true;
				$ctrl.crossReferenceToDelete = crossReference;
			};
			$ctrl.removeCrossReference = function() {
				_.remove($ctrl.crossReferences, function(crossRef) {
					return crossRef.VarietyPlusCrossReferenceID === $ctrl.crossReferenceToDelete.VarietyPlusCrossReferenceID;
				});
				$ctrl.availableCrossReferences.push($ctrl.crossReferenceToDelete);
				$ctrl.showRemoveModal = false;

				varietyPlusAdminService.saveSubcategoryCrossReferences($ctrl.subcategoryID, $ctrl.crossReferences);
			};
			$ctrl.toggleVarietyCoinVisibility = function(variety) {
				varietyPlusAdminService.saveVarietyPlusDetails(variety.CoinID, variety);
			};
			$q.all([
				varietyPlusAdminService.getSubcategory($ctrl.subcategoryID),
				varietyPlusAdminService.fetchCrossReferencesForSubcategory($ctrl.subcategoryID),
				varietyPlusAdminService.fetchAllCrossReferences(),
				$ctrl.loadVarieties(1),
			])
				.then(function(results) {
					$ctrl.subcategory = results[0];
					$ctrl.crossReferences = results[1];
					$ctrl.availableCrossReferences = _.differenceBy(
						results[2],
						$ctrl.crossReferences,
						function(x) { return x.VarietyPlusCrossReferenceID });
				});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("varietyPlusAdminService", [
		"Upload", "webServiceFactory",
		function(Upload, webServiceFactory) {
			return webServiceFactory.create({
				// Country
				fetchAllCountries: function() {
					return this.get("/admin/variety-plus/data/countries/");
				},
				getCountry: function(countryID) {
					return this.get("/admin/variety-plus/data/countries/" + countryID + "/");
				},

				// Category
				fetchCategoriesForCountry: function(countryID) {
					return this.get("/admin/variety-plus/data/countries/" + countryID + "/categories/");
				},
				fetchSubcategoriesForCategory: function(categoryID) {
					return this.get("/admin/variety-plus/data/categories/" + categoryID + "/subcategories/");
				},

				// Subcategory
				getSubcategory: function(subcategoryID) {
					return this.get("/admin/variety-plus/data/subcategories/" + subcategoryID + "/");
				},
				saveSubcategory: function(subcategoryID, subcategory) {
					return this.put("/admin/variety-plus/data/subcategories/" + subcategoryID + "/", subcategory);
				},
				fetchVarietiesForSubcategory: function(subcategoryID, page, showHidden) {
					var url = URI.expand("/admin/variety-plus/data/subcategories/{subcategoryID}/varieties/", { subcategoryID: subcategoryID })
									.addSearch("page", page)
									.addSearch("showHidden", showHidden);

					return this.get(url.toString());
				},
				fetchCrossReferencesForSubcategory: function(subcategoryID) {
					return this.get("/admin/variety-plus/data/subcategories/" + subcategoryID + "/cross-references/");
				},
				saveSubcategoryCrossReferences: function(subcategoryID, coinGroupList) {
					return this.put("/admin/variety-plus/data/subcategories/" + subcategoryID + "/cross-references/", coinGroupList);
				},

				// Cross References
				fetchAllCrossReferences: function() {
					return this.get("/admin/variety-plus/data/cross-references/");
				},
				createCrossReference: function(crossReferenceName) {
					return this.post("/admin/variety-plus/data/cross-references/", { Name: crossReferenceName });
				},
				saveCrossReference: function(crossReferenceID, crossReference) {
					return this.put("/admin/variety-plus/data/cross-references/" + crossReferenceID + "/", crossReference);
				},
				deleteCrossReference: function(crossReferenceID) {
					return this.post("/admin/variety-plus/data/cross-references/" + crossReferenceID + "/delete/");
				},
				fetchSubcategoriesForCrossReference: function(crossReferenceID) {
					return this.get("/admin/variety-plus/data/cross-references/" + crossReferenceID + "/subcategories/");
				},

				// Variety Detail
				getVarietyDetailsForCoin: function(coinID) {
					return this.get("/admin/variety-plus/data/details/" + coinID + "/");
				},
				saveVarietyPlusDetails: function(coinID, varietyPlusDetails) {
					return this.put("/admin/variety-plus/data/details/" + coinID + "/", varietyPlusDetails);
				},
				fetchCrossReferencesForCoin: function(coinID) {
					return this.get("/admin/variety-plus/data/details/" + coinID + "/cross-references/");
				},
				saveVarietyPlusDetailCrossReferences: function(coinID, varietyPlusDetailCrossReferences) {
					return this.put("/admin/variety-plus/data/details/" + coinID + "/cross-references/", varietyPlusDetailCrossReferences);
				},

				// Images
				fetchVarietyImagesForCoin: function(coinID) {
					return this.get("/admin/variety-plus/data/details/" + coinID + "/images/");
				},
				saveVarietyPlusImage: function(coinID, sortOrder, imageFile) {
					var formData = {
						sortOrder: sortOrder,
						imageFile: imageFile,
					};

					return Upload.upload({
						method: "POST",
						url: "/admin/variety-plus/data/details/" + coinID + "/images/",
						data: _.omitBy(formData, _.isNull)
					});
				},
				updateVarietyPlusImages: function(coinID, images) {
					return this.put("/admin/variety-plus/data/details/" + coinID + "/images/", images);
				},
				deleteVarietyPlusImage: function(coinID, varietyPlusImageID) {
					return this.post("/admin/variety-plus/data/details/" + coinID + "/images/" + varietyPlusImageID + "/delete/");
				},
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcCensusCategoryGrid", [
		function() {
			return {
				link: function(scope, element, attrs, ctrl) {
					ctrl.titleSelector = attrs.titleSelector;
				},
				controllerAs: "gridContainer",
				controller: [
					"$scope", "$element", "$window", "$timeout", "$http", "$q", "$compile", "urlService",
					function($scope, $element, $window, $timeout, $http, $q, $compile, urlService) {
						var self = this,
							DEFAULT_ANIMATION_LENGTH = 350,
							DATAKEY_ORIGINAL_OFFSET = "originalOffset",
							mainGrid = $element.find(".country-list"),
							mainBoxes = mainGrid.find(".grid-item"),
							childGrid, childBoxes, selectedBox, isAnimating;

						self.isOpen = false;
						self.open = function($event, url, isBootstrapped) {
							$event.preventDefault();
							if (isBootstrapped) {
								updateCache();
								mainGrid.hide();
								childGrid.css("position", "relative");
								return $q.when();
							}

							if (isAnimating)
								return $q.when();

							isAnimating = true;
							selectedBox = $($event.currentTarget).closest(".grid-item");
							var selectedItem = selectedBox.find(".grid-item-content");
							selectedItem.addClass("loading");

							// Get the data and put it in place
							return $http.get(url)
								.then(function(response) {
									childGrid = $compile(response.data)($scope);
									childBoxes = childGrid.find(".grid-item");
									childGrid.addClass("invisible");
									mainGrid.after(childGrid);
									selectedItem.removeClass("loading");
									urlService.updateWithState($event.currentTarget.attributes.href.value, null, {eventKeys: ["ngcCensusHome.closeCategories"]});
									calculateChildOffsets();
									runOpenAnimation();
								});
						};

						// Events
						$scope.$on("ngcGridContainer.close", function() {
							urlService.updateUrl(urlService.baseUrl);
							runCloseAnimation();
						});
						$scope.$on("ngcGridContainer.updateCache", updateCache);
						$window.addEventListener("popstate", function(e) {
							if (e.state && e.state.eventKeys && e.state.eventKeys.length > 0) {
								_.forEach(e.state.eventKeys, function(key) {
									$scope.$broadcast(key);
								});

								$scope.$apply();
							}
						});

						// Helper functions
						function calculateChildOffsets() {
							if (!childBoxes)
								return;

							childBoxes.each(function(i) {
								var box = $(this);
								box.data(DATAKEY_ORIGINAL_OFFSET, box.offset());
							});
						}

						function runOpenAnimation() {
							var selectedOffset = selectedBox.offset();
							var selectedPosition = selectedBox.position();

							// Animate out other boxes
							selectedBox.siblings().velocity({
								opacity: 0
							}, {
								duration: DEFAULT_ANIMATION_LENGTH
							});

							// Set up clicked box animation
							selectedBox.velocity({
								opacity: 0
							}, {
								duration: DEFAULT_ANIMATION_LENGTH * 1.5,
								delay: DEFAULT_ANIMATION_LENGTH,
								begin: function() {},
								complete: function() {
									$(self.titleSelector).velocity("scroll", {
										duration: DEFAULT_ANIMATION_LENGTH,
										mobileHA: false
									});
								}
							});

							// Animate the child boxes
							childBoxes.velocity({
								translateX: function(i) {
									return childBoxes.eq(i).data(DATAKEY_ORIGINAL_OFFSET).left - selectedOffset.left;
								},
								translateY: function(i) {
									return childBoxes.eq(i).data(DATAKEY_ORIGINAL_OFFSET).top - selectedOffset.top;
								}
							}, {
								duration: DEFAULT_ANIMATION_LENGTH,
								delay: DEFAULT_ANIMATION_LENGTH / 2,
								begin: function() {
									// Reset Velocity-cached translate values
									$.Velocity.hook(childBoxes, "translateX", "0");
									$.Velocity.hook(childBoxes, "translateY", "0");

									childGrid
										.removeClass("invisible")
										.height(childGrid.height());

									childBoxes
										.css("opacity", "")
										.css("position", "absolute")
										.css("top", selectedPosition.top)
										.css("left", selectedPosition.left);
								},
								complete: function() {
									childGrid.height("").css("position", "relative");

									mainGrid.hide();

									childBoxes
										.css("position", "")
										.css("top", "")
										.css("left", "")
										.css("transform", "");

									isAnimating = false;
									self.isOpen = true;
								}
							});
						}

						function runCloseAnimation() {
							if (isAnimating)
								return;

							isAnimating = true;

							childBoxes.velocity({
								opacity: 0
							}, {
								duration: DEFAULT_ANIMATION_LENGTH,
								complete: function() {
									mainGrid.css('display', '');
								}
							});

							mainBoxes.velocity({
								opacity: 1
							}, {
								duration: DEFAULT_ANIMATION_LENGTH,
								delay: DEFAULT_ANIMATION_LENGTH / 2,
								begin: function() {
									mainBoxes
									  .css('position', '')
									  .css('top', '')
									  .css('left', '')
									  .css('display', '');
								},
								complete: function() {
									mainBoxes.css('opacity', '');
									isAnimating = false;
									childGrid.remove();
								}
							});
						}

						function updateCache() {
							childGrid = $element.find("ul.grid:last-child");
							childBoxes = childGrid.find(".grid-item");
						}

					}
				]
			};
		}]);
"use strict";
angular.module("NGC.Registry")
	.directive("ngcCensusDenoms", [
		"$rootScope", "$timeout", "ngcScrollService", "urlService",
		function($rootScope, $timeout, ngcScrollService, urlService) {
			return {
				scope: {
					id: "@ngcCensusDenoms",
					name: "@itemName",
					seoName: "@",
					parentName: "@",
					baseUrl: "@",
				},
				templateUrl: "template-cache/census/denominations.html",
				transclude: true,
				replace: true,
				bindToController: true,
				controllerAs: "denomCtrl",
				controller: [
					"$scope", "$element", "$http",
					function($scope, $element, $http) {
						var self = this;
						var isLoaded = false;
						var isOpen = false;

						self.show = function() {
							$rootScope.$broadcast("ngcCensusDenoms.hide." + self.parentName, self.seoName);
							self.getDenoms();
							urlService.replaceUrl(self.thisUrl);
							$scope.$emit("page.metadata.update", "census-subcountry", self.name, self.parentName, self.seoName);
							$timeout(function() {
								$element.attr("open", "");
								ngcScrollService.performScroll($element, 250);
								isOpen = true;
							});
						};

						self.hide = function(skipMetadataUpdate) {
							urlService.replaceUrl(self.parentUrl);
							if(!skipMetadataUpdate)
								$scope.$emit("page.metadata.update", "census");

							$timeout(function() {
								$element.removeAttr("open");
								isOpen = false;
							});
						};

						self.toggle = function() {
							if(isOpen)
								self.hide();
							else self.show();
						};

						self.getDenoms = function() {
							if(isLoaded)
								return;

							$http.get(urlService.root + "resources/services/census/denominations/subcountry/" + self.id)
								.then(function(response) {
									var denoms = response.data;
									var length = denoms.length;
									if(length > 5) {
										var middle = Math.round(length / 2);
										self.columns = [
											response.data.slice(0, middle),
											response.data.slice(middle, length),
										];
									} else {
										self.columns = [response.data];
									}

									$scope.$emit("ngcGridItem.relayout");
								});

							isLoaded = true;
						};
					}
				],
				link: function(scope, elem, attrs, ctrl) {
					$rootScope.$on("ngcCensusDenoms.hide." + ctrl.parentName, function(e, selectedName) {
						if(selectedName !== ctrl.seoName)
							ctrl.hide(true);
					});

					ctrl.parentUrl = ctrl.baseUrl + ctrl.parentName + "/";
					ctrl.thisUrl = ctrl.parentUrl + ctrl.seoName + "/";

					if(urlService.path() === ctrl.thisUrl) {
						$timeout(function() {
							ctrl.toggle();
						});
					}
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("censusHomeController", [
		"$scope", "$rootScope", "$timeout", "censusControllerData",
		function($scope, $rootScope, $timeout, censusControllerData) {
			var ctrl = this;

			// Load bootstrapped values
			if (censusControllerData.selectedCountry) {
				ctrl.selectedCountry = censusControllerData.selectedCountry;
				ctrl.isOpen = censusControllerData.isBootstrapped;
				ctrl.preloaded = censusControllerData.isBootstrapped;

				// Hide the main grid
				$('.grid.country-list:first').hide();

				// Set correct display values for child grid
				$('.grid.country-category-list').css('position', 'relative');

				$timeout(function() {
					$scope.$broadcast("ngcGridContainer.updateCache");
				}, 0);
			}

			ctrl.openCategories = function($event, gridCtrl, countryName, url, seoFriendly) {
				gridCtrl.open($event, url, ctrl.preloaded)
					.then(function() {
						if (!ctrl.preloaded)
							$scope.$emit("page.metadata.update", "census-country", countryName, seoFriendly);

						ctrl.preloaded = false;
						ctrl.isOpen = true;
						ctrl.selectedCountry = countryName;
					});
			};
			ctrl.closeCategories = function($event) {
				$event.preventDefault();
				$scope.$broadcast("ngcGridContainer.close");
				ctrl.isOpen = false;
				$scope.$emit("page.metadata.update", "census");
			};

			$scope.$on("ngcCensusHome.closeCategories", ctrl.closeCategories);
		}
	]);;
"use strict";
angular.module("NGC.Registry")
	.controller("censusSearchController", [
		"$http", "$q", "urlService", "censusSearchService",
		function($http, $q, urlService, censusSearchService) {
			var self = this;

			self.searchCoins = function(searchText) {
				if(!searchText.length) {
					return;
				}

				var pathSegments = new URI().segment();
				var categoryType = pathSegments[0];
				var country = _(pathSegments).without("census", "price-guide", "world").first();

				self.loading = true;

				return censusSearchService.search(searchText, categoryType, country)
					.finally(function() {
						self.loading = false;
					});
			};

			self.coinSelected = function(coin) {
				if(!coin || coin.CensusListingUrl == "")
					return;

				window.location = urlService.baseUrl + coin.CensusListingUrl;
			};
	}]);
;
"use strict";
angular.module("NGC.Registry")
	.provider("censusSearchService", [
		function() {
			var censusSearchService = {
				previousSearches: {},
				noResultsText: "No results found",
				$get: [
					"$q", "webServiceFactory",
					function($q, webServiceFactory) {
						return webServiceFactory.create({
							search: function(searchText, categoryType, country) {
								if(!searchText.length) {
									return $q.when([]);
								}

								// Clean up the search text, since it goes in the url.
								searchText = searchText.replace("/", "-");

								// Check the cache first.
								var cacheKey = country + "-" + searchText;
								var cachedResults = censusSearchService.previousSearches[cacheKey];
								if(cachedResults)
									return $q.when(cachedResults);

								// Build the search url.
								var searchUrl = "~/resources/services/coin-search/" + categoryType + "/search/";
								if(country) { searchUrl += country + "/"; }
								searchUrl += searchText + "/";

								return this.get(searchUrl)
									.then(function(results) {
										if(results && results.length > 0) {
											censusSearchService.previousSearches[cacheKey] = results;
										} else {
											results = [{
												CensusListingUrl: "",
												CoinDescription: censusSearchService.noResultsText,
												CoinID: 0,
												IsUS: false,
											}];
										}

										return results;
									});
							}
						});
					}
				]
			};

			return censusSearchService;
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcCoinGridDataView", [
		function() {
			return {
				scope: {
					viewName: "@ngcCoinGridDataView",
					startingView: "@",
					gridKey: "@",
					highlightDelay: "@",
					preloaded: "@"
				},
				controllerAs: "coinGridDataViewCtrl",
				controller: [
					"$scope", "$rootScope", "$element", "$interval", "$compile", "urlService", "coinGridService", "coinGridData", "coinGridBreadcrumbData",
					function($scope, $rootScope, $element, $interval, $compile, urlService, coinGridService, coinGridData, coinGridBreadcrumbData) {
						var self = this;
						var cache = {};
						var urlRegex = /\/(census|price-guide)\//;
						var highlightInterval, disableHighlight, lastCell, previousColIndex, dataTable, skipScroll;

						self.createHighlightEvents = function() {
							// Add highlight events
							$element.on("mouseenter", "td", function() {
								self.highlightCell($(this));
							});
							$element.on("mouseleave", function () {
								if (disableHighlight)
									return;

								$interval.cancel(highlightInterval);
							});
						};
						self.currentView = $scope.startingView;
						self.changeView = function(e, view, skipChangeUrl, skipLoader) {
							if(!_.includes(["census", "price-guide", "details-census"], view)) {
								return;
							}

							if ($scope.preloaded) {
								self.createHighlightEvents();
								return;
							}

							var breadcrumbs = angular.copy(coinGridBreadcrumbData);
							if (view.indexOf("census") >= 0) {
								delete breadcrumbs["price-guide"];
								delete breadcrumbs[(view === "census") ? "details-census" : "census"];
							} else {
								delete breadcrumbs["census"];
								delete breadcrumbs["details-census"];
							}

							//temporarily set the breadcrumbs link to point to to census as we are not releasing details census in initial release
							var breadcrumbsView = (view === "price-guide") ? "price-guide" : "census";
							var $gridToggles = $(".grid-toggles[show-loading-spinner]");
							breadcrumbs["page"].url = breadcrumbs["page"].url.replace(urlRegex, "/" + breadcrumbsView + "/");
							$scope.$emit("ngcBreadcrumbBar.update", breadcrumbs);
							self.toggleLoading(true);

							if (!skipLoader) {
								$element.addClass("view-switching");
								$gridToggles.addClass("loading");
								$element.find("table").remove();
							}

							self.resizeToParent();

							coinGridService.getGridView(urlService.path(), view)
								.then(function(content) {
									cache[view] = content;
									$element.removeClass("view-switching");
									$gridToggles.removeClass("loading");
									self.updateView(view, skipChangeUrl, content, true);
								});
						};
						self.highlightCell = function(cell) {
							if(disableHighlight)
									return;

							if(cell.parent().attr("class").indexOf("numeric-total") >= 0)
								return;

							$interval.cancel(highlightInterval);
							highlightInterval = $interval(function () {
								if(lastCell && lastCell.attr("class").indexOf("persist") === -1) {
									lastCell.removeClass("focused");
								}

								var colIndex = cell.index();
								if(!_.isNil(previousColIndex) && colIndex !== previousColIndex) {
									self.highlightColumn(previousColIndex, "removeClass");
								}

								self.highlightColumn(colIndex, "addClass");
								cell.addClass("focused");

								// Update tracking objects
								previousColIndex	= colIndex;
								lastCell            = cell;
							}, $scope.highlightDelay || 0, 1);
						};
						self.highlightColumn = function(colIndex, command) {
							var gradeColumns = $element.find("td:nth-child(" + (colIndex + 1) + ")");
							var headerColumns = $(".scrollable").find("th:nth-child(" + (colIndex + 1) + ")");

							gradeColumns.each(function() {
								if ($(this).parent().attr("class").indexOf("numeric-total") === -1 && $(this).parent().attr("class").indexOf("row-highlight") === -1) {
									$(this).toggleClass("highlight", command === "addClass");
								}
							});

							//use one class for the header for now no matter it persists or not
							headerColumns.each(function() {
								if ($(this).parent().attr("class").indexOf("summary-header-row") === -1 && $(this).attr("class").indexOf("request-grade") === -1 && $(this).parent().attr("ngc-coin-grid-row") === $scope.gridKey) {
									$(this).toggleClass("highlight", command === "addClass");
								}
							});
						};
						self.resizeToParent = function() {
							$element.height($('.pinned').height());
						};
						self.toggleLoading = function(isLoading) {
							coinGridService.isLoading = isLoading;
						};
						self.updateView = function(view, skipChangeUrl, content, skipScopeApply) {
							// Retrieve and update the metadata and url
							if (!skipChangeUrl) {
								$scope.$emit("page.metadata.update", view + "-listing", $scope.viewName, urlService.fullPath());
								urlService.replaceUrl(urlService.fullPath().replace(urlRegex, '/' + view + "/"));
							}

							// Destroy any previous content
							$rootScope.$broadcast("ngcCoinGridRow.destroy." + $scope.gridKey);

							// Load the content into the DOM
							$element.find("table").remove();
							dataTable = $compile(content)($scope.$new());
							dataTable.appendTo($element);
							self.createHighlightEvents();

							// Fire change events
							$scope.$emit("ngcCoinViewMenu.close");
							$scope.$emit("ngcCoinGridDataView.viewSwitched." + $scope.gridKey, view, skipScopeApply, !skipScroll);
							$rootScope.$broadcast("ngcCoinGrid.reload." + $scope.gridKey);

							// Reload sticky header in next digest cycle so the content will be loaded
							$interval(function() {
								$scope.$emit("ngcCoinGridStickyHeader.reload." + $scope.gridKey);
							}, 0, 1);

							// Set the state
							$element
								.removeClass("loading")
								.css("height", "");

							self.currentView = view;
							skipScroll = true;
							self.toggleLoading(false);
						};

						// Run initial view switch in interval, to allow time for other components to load
						$interval(function() {
							self.changeView(null, $scope.startingView, true, true);
						}, 0, 1);

						$scope.$on("ccgViewSwitch.viewSwitched", self.changeView);
						$scope.$watch(function() {
							return coinGridService.isLoading;
						}, function(newValue, oldValue) {
							$scope.$emit("ngcLoadingOverlay.toggle", coinGridService.isLoading);
						});
						$rootScope.$on("ccgDragToScroll.beginScroll", function(e) {
							disableHighlight = true;
						});
						$rootScope.$on("ccgDragToScroll.endScroll", function(e) {
							disableHighlight = false;
						});
					}
				]
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcCoinGridFilterTrigger", [
		"$rootScope", "coinGridService", "domService",
		function ($rootScope, coinGridService, domService) {
			return {
				scope: {},
				link: function (scope, elem, attrs, ctrl) {
					var isOpen = coinGridService.getOpen();
					var toggleClass = function() {
						if (isOpen) {
							elem.addClass("selected")
							$("html").toggleClass("no-scroll", domService.isXSmall());
						}
						else {
							elem.removeClass("selected");
							$("html").removeClass("no-scroll");
						}
					};

					elem.on("click", function(e) {
						if (isOpen) {
							$("html").removeClass("no-scroll");
							$rootScope.$broadcast("ngcCoinGridFilter.close." + attrs.gridKey);
						} else {
							$("html").toggleClass("no-scroll", domService.isXSmall());
							$rootScope.$broadcast("ngcCoinGridFilter.open." + attrs.gridKey);
						}

						isOpen = !isOpen;
						toggleClass();
					});

					toggleClass();

					$rootScope.$on("ngcCoinGridFilterTrigger.toggle", function(e, open) {
						isOpen = open;
						toggleClass();
					});
				}
			};
		}
	]);;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcCoinGridFilter", [
		"hotkeys", "coinGridService",
		function(hotkeys, coinGridService) {
			return {
				scope: {
					gridKey: "@",
					noPersist: "@",
					defaultView: "@",
					designationOverride: "@"
				},
				replace: true,
				templateUrl: "template-cache/census/coin-grid-filter.html",
				controllerAs: "coinGridFilter",
				controller: [
					"$scope", "$rootScope", "$element", "$interval", "$document",
					function($scope, $rootScope, $element, $interval, $document) {
						var self = this;
						var selects = ["[name=fromGrade]", "[name=toGrade]", "[name=fromYear]", "[name=toYear]"];
						var dkSelects = [];
						var params = (new URI()).search(true);
						var fromNGCResearch = params["from"] && params["from"].toLowerCase() === "ngcresearch";

						self.isDetailsCensus = $scope.defaultView === "details-census";
						self.currentView = $scope.defaultView;

						self.onDocumentClick = function(event) {
							var isChild = $element.find(event.target).length;
							var isFilterTab = $(event.target).parents().filter('.grid-filters').length;
							var isFilterToggle = $(event.target).is(".icon-filter");

							//only close if we are clicking outside of the filterToggle button and the filter itself
							//filter is not saved for varieties, details, and details-census panes, hence "open" checks have to be done separately, depending on the gridKey
							if (!isChild && !isFilterToggle
								&& (($scope.gridKey == "main" && coinGridService.getOpen())
								|| !isFilterToggle && ($scope.gridKey != "main" && !isFilterTab))) {
								self.close();
								$rootScope.$broadcast("ngcCoinGridFilter.close." + $scope.gridKey);
							}
						};
						$document.bind("click", self.onDocumentClick);

						self.applyAll = function() {
							self.updateYearLists(true);
							checkDropdowns();
							executeFilter("ngcCoinGridFilter.applyFilters");
						};
						self.filterByDesignation = function() {
							self.updateYearLists(false);
							checkDropdowns();
							executeFilter("ngcCoinGridFilter.filterByDesignation");
						};
						self.filterByGrade = function() {
							checkDropdowns();
							executeFilter("ngcCoinGridFilter.filterByGrade");
						};
						self.filterByGradeModifier = function() {
							executeFilter("ngcCoinGridFilter.filterByGradeModifier");
						};
						self.filterByYear = function() {
							executeFilter("ngcCoinGridFilter.filterByYear");
						};
						self.toggleTotals = function() {
							executeFilter("ngcCoinGridFilter.toggleTotals");
						};
						self.toggleUndated = function() {
							executeFilter("ngcCoinGridFilter.toggleUndated");
						};

						self.close = function() {
							self.toggleOpen(false);
							$rootScope.$broadcast("ngcCoinGridFilterTrigger.toggle", false);
						};
						self.changeView = function(view) {
							self.isDetailsCensus = view === "details-census";
							self.coinGradeList = coinGridService.getGradeList(view).map(function(grade) {
								return { grade: grade };
							});
							self.fromGradeList = self.coinGradeList;
							self.toGradeList = self.coinGradeList;
							var viewChanged = self.currentView != view;
							var gradeSuffix = (self.isDetailsCensus ? "Details" : "");
							self.fromGrade = viewChanged ? self.coinGradeList[0] : _.find(self.fromGradeList, { grade: self.filterValues["fromGrade" + gradeSuffix] }) || self.coinGradeList[0];
							self.toGrade = viewChanged ? self.coinGradeList[self.coinGradeList.length - 1] : _.find(self.toGradeList, { grade: self.filterValues["toGrade" + gradeSuffix] }) || self.coinGradeList[self.coinGradeList.length - 1];

							// Correct census/price-guide grade discrepancies
							switch (view) {
								case "census":
									if (self.fromGrade === "XF")
										self.fromGrade = "40";
									if (self.toGrade === "XF")
										self.toGrade = "40";
									break;
								case "price-guide":
									if (self.fromGrade === "40" || self.fromGrade === "45")
										self.fromGrade = "XF";
									if (self.toGrade === "40" || self.toGrade === "45")
										self.toGrade = "XF";
									break;
							}

							self.currentView = view;
							self.applyAll();
						};
						self.toggleOpen = function(open) {
							self.isOpen = (open === undefined) ? !self.isOpen : open;

							if ($scope.gridKey === "main" && $scope.noPersist === undefined) {
								coinGridService.setOpen(self.isOpen);
							}
						};
						self.updateDropdowns = function() {
							var fromGradeIndex = self.coinGradeList.indexOf(self.fromGrade);
							var toGradeIndex = self.coinGradeList.indexOf(self.toGrade);
							self.fromGradeList = self.coinGradeList.filter(function(value, index) { return index <= toGradeIndex; });
							self.toGradeList = self.coinGradeList.filter(function(value, index) { return index >= fromGradeIndex; });
						};
						self.updateYearLists = function(rememberSelection) {
							self.coinYearList = coinGridService.getCoinYearList(self.filterValues.designation);
							self.fromYearList = self.coinYearList;
							self.toYearList = self.coinYearList;

							if (self.coinYearList.length > 0) {
								var originalFromYear = null;
								var originalToYear = null;

								if(rememberSelection) {
									originalFromYear = _.get(self.filterValues.fromYear, 'year');
									originalToYear = _.get(self.filterValues.toYear, 'year');
								}

								self.filterValues.fromYear = _.find(self.coinYearList, { year: originalFromYear }) || _.head(self.coinYearList);
								self.filterValues.toYear = _.findLast(self.coinYearList, { year: originalToYear }) || _.last(self.coinYearList);
							}
						};
						self.load = function(view, firstLoad) {
							// Toggles loading class to prevent animation
							self.loading = true;
							self.isOpen = coinGridService.getOpen();
							self.filterValues = coinGridService.getFilters(firstLoad && fromNGCResearch);
							self.isMainGrid = $scope.gridKey === "main";
							self.updateYearLists(true);
							self.changeView(view);
							self.toggleOpen(self.isOpen);

							if (firstLoad) {
								self.filterValues.designation = $scope.designationOverride || self.filterValues.designation;
								self.filterValues.fromYear = $scope.fromYearOverride || self.filterValues.fromYear;
								self.filterValues.toYear = $scope.toYearOverride || self.filterValues.toYear;

								// Assign the designation to the parent scope so that the view-switcher that toggles designation can stay in sync.
								_.set($scope.$parent, 'designation.view', self.filterValues.designation);

								self.applyAll();
							}

							// Remove loading class to enable animation
							// Use interval so the flag is toggled in a different digest cycle
							$interval(function() {
								self.loading = false;
							}, 0, 1);
						};

						function executeFilter(keyPrefix) {
							if ($scope.noPersist === undefined)
								coinGridService.saveFilters(self.filterValues);

							$rootScope.$broadcast(keyPrefix + "." + $scope.gridKey, self.filterValues);
						}

						function checkDropdowns() {
							if (self.isDetailsCensus) {
								self.filterValues.fromGradeDetails = self.fromGrade.grade;
								self.filterValues.toGradeDetails = self.toGrade.grade;
							} else {
								self.filterValues.fromGrade = self.fromGrade.grade;
								self.filterValues.toGrade = self.toGrade.grade;
							}

							self.updateDropdowns();
						}
					}
				],
				link: function(scope, elem, attrs, ctrl) {
					ctrl.load(scope.defaultView, true);

					if(scope.gridKey) {
						scope.$on("ngcCoinGridFilter.open." + scope.gridKey, function() {
							ctrl.toggleOpen(true);
							scope.$apply();
						});
						scope.$on("ngcCoinGridFilter.close." + scope.gridKey, function() {
							ctrl.toggleOpen(false);
							scope.$apply();
						});
					}

					scope.$on("ngcCoinGridFilter.changeView." + scope.gridKey, function(e, view) {
						ctrl.changeView(view);
					});
					scope.$on("ccgViewSwitch.viewSwitched", function(e, view) {
						if(_.includes(["MS", "PF"], view)) {
							ctrl.filterValues.designation = view;
							ctrl.filterByDesignation();
						}
					});

					// Blur inputs on click so the hotkey can work without the user defocusing the element
					var inputs = elem.find("input");
					inputs.on("click", function() {
						inputs.blur();
					});
				}
			};

		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcCoinGridRow", [
		"coinGridService",
		function(coinGridService) {
			return {
				scope: true,
				link: function(scope, elem, attrs) {
					var gridKey = attrs.ngcCoinGridRow;
					var grades = {};

					// this file exists to add census counts when collapsing star/plus columns

					function formatCell(value) {
						if(value == 0)
							return "";

						return value.toLocaleString(CCG.culture, { minimumFractionDigits: 0 });
					}
					// Cache initial values
					elem.find("td").each(function() {
						var isStar = false,
							isPlus = false,
							isPlusStar = false,
							isLink = false,
							grade = "";

						_.forEach(this.classList, function(cssClass) {
							if (cssClass === "plus")
								isPlus = true;
							else if (cssClass === "star")
								isStar = true;
							else if (cssClass === "plus-star")
								isPlusStar = true;
							else if (cssClass === "link")
								isLink = true;
							else if (cssClass.indexOf("grade-") === 0)
								grade = cssClass.substr(6);
						});

						var value = parseInt((this.textContent || this.innerText).replace(/\D/g,"")) || 0;
						var gradeObj = grades[grade] || {};

						if (isPlusStar){
							gradeObj.plusStar = value;
							gradeObj.plusStarLink = isLink;
						} else if (isPlus) {
							gradeObj.plus = value;
							gradeObj.plusLink = isLink;
						} else if (isStar) {
							gradeObj.star = value;
							gradeObj.starLink = isLink;
						} else {
							gradeObj.base = value;
							gradeObj.baseLink = isLink;
						}

						grades[grade] = gradeObj;
					});

					scope.$on("ngcCoinGridRow.update." + gridKey, function(e, currentFilters) {
						var filters = currentFilters || coinGridService.getFilters();
						var isBase = filters.base;
						var isPlus = filters.plus;
						var isStar = filters.star;
						var showAll = isBase && isPlus && isStar || !isBase && !isPlus && !isStar;

						for (var prop in grades) {
							var gradeObj = grades[prop];
							var displayObj;
							var base = gradeObj.base || 0;
							var plus = gradeObj.plus || 0;
							var star = gradeObj.star || 0;
							var plusStar = gradeObj.plusStar || 0;

							if (!showAll && !(isPlus && isStar)) {
								if (isPlus) {
									base = base + star;
									plus = plus + plusStar;
									star = 0;
									plusStar = 0;
								} else if (isStar) {
									base = base + plus;
									star = star + plusStar;
									plus = 0;
									plusStar = 0;
								} else if (isBase) {
									base = base + plus + star + plusStar;
									star = 0;
									plus = 0;
									plusStar = 0;
								}
							}

							// Update values only if it changed and that grade filter is shown
							var cells = elem.find("td.grade-" + prop);
							if (gradeObj.lastBase !== base && (showAll || isBase)) {
								if (gradeObj.baseLink)
									cells.filter(".base").find("a").text(formatCell(base) || "");
								else cells.filter(".base").text(formatCell(base) || "");
							}
							if (gradeObj.lastPlus !== plus && (showAll || isPlus)) {
								if (gradeObj.plusLink)
									cells.filter(".plus").find("a").text(formatCell(plus) || "");
								else cells.filter(".plus").text(formatCell(plus) || "");
							}
							if (gradeObj.lastStar !== star && (showAll || isStar)) {
								if (gradeObj.starLink)
									cells.filter(".star").find("a").text(formatCell(star) || "")
								else cells.filter(".star").text(formatCell(star) || "");
							}
							if (gradeObj.lastPlusStar !== plusStar && (showAll || (isPlus && isStar))) {
								if (gradeObj.plusStarLink)
									cells.filter(".plus-star").find("a").text(formatCell(plusStar) || "")
								else cells.filter(".plus-star").text(formatCell(plusStar) || "");
							}
							// Track latest values
							gradeObj.lastBase = base;
							gradeObj.lastPlus = plus;
							gradeObj.lastStar = star;
							gradeObj.lastPlusStar = plusStar;

						}

					});
					
				}
			};
		}
	]);
	;
"use strict";

angular.module("NGC.Registry")
	.service("coinGridService", [
		"storageService", "ngcHttpCacheService", "coinGrades", "coinFilterData",
		function(storageService, httpCache, coinGrades, coinFilterData) {
			var viewRegex		= /\/(census|price-guide)\//;
			var varietiesRegex	= /varieties\/\d+\/?/;
			var detailsRegex	= /details\/\d+\/?/;
			var historyRegex    = /history\/\d+\/[a-zA-Z0-9]+\/?/;

			return {
				OPEN_KEY: "coin-grid-filter.open",
				VALUE_KEY: "coin-grid-filter.values",
				formatOverlayUrl: _.template("${type}${category}/coin-detail/${subcatId}/${partialUrl}"),
				isLoading: false,
				getOpen: function() {
					return !!storageService.get(this.OPEN_KEY);
				},
				setOpen: function(isOpen) {
					storageService.set(this.OPEN_KEY, isOpen);
				},
				getCoinYearList: function(designation) {
					var years = coinFilterData.yearLists[designation.toLowerCase()];
					return years.map(function(year) {
						return { year: year };
					});
				},
				getDesignations: function() {
					return coinFilterData.designations;
				},
				getGridView: function(currentUrl, view) {
					var dataUrl = currentUrl
						.replace(varietiesRegex, '')
						.replace(detailsRegex, '')
						.replace(historyRegex, '')
						.replace(viewRegex, "/" + view + "/") + "data/";

					return httpCache.get(dataUrl);
				},
				getGradeList: function(view) {
					return coinGrades[view];
				},
				getFilters: function(selectAll) {
					var filterValues = storageService.get(this.VALUE_KEY) || {
						base: true,
						star: false,
						plus: false,
						total: false,
						undated: true,
						designation: "MS",
						fromGrade: "PrAg",
						toGrade: "70",
						fromGradeDetails: "PrAg",
						toGradeDetails: "UNC"
					};

					if(selectAll) {
						filterValues = {
							base: true,
							star: true,
							plus: true,
							total: true,
							undated: true,
							designation: "MS",
							fromGrade: "PrAg",
							toGrade: "70",
							fromGradeDetails: "PrAg",
							toGradeDetails: "UNC",
						};
					}

					// Designation is special because it syncs via a view-switch directive
					filterValues.designation = storageService.get("designation") || "MS";

					// if persisted designation does not exist default to first available
					var designations = this.getDesignations();
					if(designations.length > 0 && designations.indexOf(filterValues.designation) < 0) {
						filterValues.designation = designations[0];
					}

					return filterValues;
				},
				saveFilters: function(filterValues) {
					// Copy the object since we need to remove non-persistent properties
					var clone = angular.copy(filterValues);
					delete clone.fromYear;
					delete clone.toYear;

					storageService.set(this.VALUE_KEY, clone);
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcCoinGridStickyHeader", [
		"$compile", "domService", "coinGridService", "ngcScrollService",
		function($compile, domService, coinGridService, ngcScrollService) {
			return {
				link: function(scope, elem, attrs, ctrl) {
					var self = this;
					var mainContent, popGrid, scrollable, pinnedTable, scrollTable;
					var pinnedHead, scrollHead, headerScrollTable;
					var fixed, offset;
					var wrapperOuter = $("<div>").addClass(attrs.wrapperClasses);
					var wrapperInner = $("<div>").addClass(attrs.mainGridClass);
					var hasSideBar = attrs.hasSideBar === "true";
					if (attrs.gridClasses !== undefined)
						wrapperInner.addClass(attrs.gridClasses);

					var showListener = scope.$on("ngcCoinGridStickyHeader.show." + attrs.gridKey, function(e) { toggle(true); });
					var hideListener = scope.$on("ngcCoinGridStickyHeader.hide." + attrs.gridKey, function(e) { toggle(false); });
					var reloadListener = scope.$on("ngcCoinGridStickyHeader.reload." + attrs.gridKey, load);

					scope.$on("$destroy", function() {
						// Call to deregister
						showListener();
						hideListener();
						reloadListener();
						ngcScrollService.deregisterOffsetElement(elem);
					});

					load();

					function load() {
						refresh();

						if (fixed) {
							createHeader();
							applyOffset();
						}
					}
					function toggle(isFixed) {
						//do not display the sticky header if only the header row is in the table
						if(scrollTable[0] && scrollTable[0].rows.length === 1) {
							return;
						}

						fixed = (isFixed !== undefined) ? isFixed : !fixed;

						if(fixed) {
							refresh();
							createHeader();
							applyOffset();
						} else {
							destroyHeader();
						}
					}

					function applyOffset() {
						offset = pinnedTable.width() + parseInt((mainContent.css("padding-left") || "").replace("px", "") || 0);

						if (!domService.isSmall() && !domService.isXSmall() && !isNaN(attrs.additionalOffset))
							offset += parseFloat(attrs.additionalOffset);

						if(scrollable[0])
							offset = scrollable[0].scrollLeft - offset;
						if(hasSideBar)
							offset = offset - mainContent.offset().left;	
						
						headerScrollTable.offset({
							left: -offset
						});
					}

					function createHeader() {
						wrapperOuter.appendTo(attrs.appendTo);

						wrapperInner
							.append(pinnedHead)
							.append(scrollHead)
							.appendTo(wrapperOuter);

						scrollable.on("scroll", applyOffset);
						$(window).on("resize", load);
						scrollable.trigger("scroll");
						ngcScrollService.registerOffsetElement(elem);
					}
					function destroyHeader() {
						wrapperOuter.remove();
						wrapperOuter.html("");
						wrapperInner.html("");
						if (scrollable)
							scrollable.off("scroll", applyOffset);

						$(window).off("resize", load);
						ngcScrollService.deregisterOffsetElement(elem);
					}

					function prepHeader(originalTable, cssClass, isScrollable) {
						var $header;
						if(attrs.isDetails === "true" && attrs.gridKey === "details") {
							$header = $(originalTable.find("tr").slice(0, 3));
						} else {
							$header = originalTable.find("tr.header-row");
						}
							
						var widths = $header.find("th").map(function(index) {
							return this.getBoundingClientRect().width;
						});

						var $newheader = $header.clone();

						var $wrapper = $("<div>")
							.addClass("pinned-header-wrap")
							.addClass(cssClass);

						if ($newheader.length === 0)
							$wrapper.addClass("empty");

						$("<table>")
							.append($newheader)
							.appendTo($wrapper);

						$newheader.find("th").each(function(index) {
							$(this).css(attrs.isDetails === "true"? "width" : "min-width", widths[index]);
						});

						if (isScrollable) {
							var loading = $("<div ngc-loading-overlay></div>");
							$wrapper.append($compile(loading)(scope));

							// May need to trigger the loader to show since we just added it to the DOM
							if (coinGridService.isLoading)
								scope.$emit("ngcLoadingOverlay.toggle", true);
						}

						return $wrapper;
					}
					function refresh() {
						destroyHeader();
						mainContent = $(attrs.container);
						popGrid = mainContent.find("." + attrs.mainGridClass);
						scrollable = popGrid.find(".scrollable");
						pinnedTable = popGrid.find(".pinned table:first-child");
						scrollTable = popGrid.find(".scrollable table:first-child");
						pinnedHead = prepHeader(pinnedTable, "pinned");
						scrollHead = prepHeader(scrollTable, "scrollable", true);
						headerScrollTable = scrollHead.find("table");
					}
				}
			};
		}
	]);;
"use strict";
angular.module("NGC.Registry")
	.controller("coinGridSwitchController", [
		"$scope", "urlService",
		function ($scope, urlService) {
			var ctrl = this;
			var currentUrl = urlService.path();

			var varietiesRegex = /varieties\/\d+\/?/;
			var detailsRegex = /details\/\d+\/?/;
			var priceHistoryRegex = /history\/\d+\/[0-9a-zA-Z]+\/?/;
			ctrl.isCensus = _.includes(currentUrl, "census");
			ctrl.censusUrl = currentUrl.replace(/\/(details-census|price-guide)\//, "/census/").replace(varietiesRegex, '').replace(detailsRegex, '').replace(priceHistoryRegex, '');
			ctrl.detailsCensusUrl = currentUrl.replace(/\/(census|price-guide)\//, "/details-census/").replace(varietiesRegex, '').replace(detailsRegex, '').replace(priceHistoryRegex, '');

			$scope.$on("ccgViewSwitch.viewSwitched", function (e, view) {
				if(_.includes(["census", "price-guide"], view)) {
					ctrl.isCensus = (view === "census");
				}
			});
		}
	]);
;
"use strict";

angular.module("NGC.Registry")
	.directive("ngcCoinGrid", [
		"$rootScope", "$document", "$interval", "$compile", "windowWatcher", "ngcWindowService",
		function($rootScope, $document, $interval, $compile, windowWatcher, ngcWindowService) {
			return {
				scope: {
					gridKey: "@",
					defaultView: "@",
					useYearFilters: "=",
					parentSelector: "@",
					headerSelector: "@",
				},
				transclude: true,
				replace: true,
				controllerAs: "coinGridCtrl",
				controller: [
					"$scope", "$element", "coinGridService", "styleService", "ngcScrollService",
					function($scope, $element, coinGridService, styleService, ngcScrollService) {
						var self        = this;
						var views       = ["census", "price-guide", "details-census"];
						var VALUE_KEY   = "coin-grid-filter.values";
						var rowFilter   = function(elem, i) { return i % 2 !== 0; };
						var altFilter   = function(elem, i) { return i % 2 === 0; };
						var params      = (new URI()).search(true);
						var gradeStyles = [];
						var gridStyles  = {
							base: $scope.parentSelector + " .base { display: none; }",
							plus: $scope.parentSelector + " .plus { display: none; }",
							star: $scope.parentSelector + " .star { display: none; }",
							plusStar: $scope.parentSelector + " .plus-star { display: none; }",
						};
						var headerStyles = {
							base: $scope.headerSelector + " .base { display: none; }",
							plus: $scope.headerSelector + " .plus { display: none; }",
							star: $scope.headerSelector + " .star { display: none; }",
							plusStar: $scope.headerSelector + " .plus-star { display: none; }",
						};

						var firstFilter, $pinnedContainer, $pinnedTable, $pinnedRows, $pinnedHeader;
						var $scrollableContainer, $scrollableTable, $scrollableRows, $scrollableHeader, $scrollableCols;
						self.currentView = $scope.defaultView;
						self.isDetailsCensus = (self.currentView === "details-census");

						// Helper functions
						self.setRowStyles = function() {
							var rows = [], altRows = [];

							var pinnedArray = $pinnedRows.filter(":visible").toArray();
							var scrollArray = $scrollableRows.filter(":visible").toArray();

							rows.push(pinnedArray.filter(rowFilter), scrollArray.filter(rowFilter));
							altRows.push(pinnedArray.filter(altFilter), scrollArray.filter(altFilter));

							$(Array.prototype.concat.apply([], rows)).removeClass("alt");
							$(Array.prototype.concat.apply([], altRows)).addClass("alt");

							var highlightedRowIndex = $pinnedTable.find(".row-highlight").index();
							if(highlightedRowIndex >= 0)
								$scrollableTable.find("tr:nth-child(" + (highlightedRowIndex + 1) + ")").addClass("row-highlight");
						};
						self.setRowHeights = function() {
							if(!$scrollableRows.length)
								return;

							var pinnedRows = $pinnedRows.filter(":visible:not(.header-row)").toArray();
							var scrollRows = $scrollableRows.filter(":visible:not(.header-row)").toArray();
							var heights = pinnedRows.map(function(row, index) {
								return {
									index: index,
									row: scrollRows[index],
									height: row.getBoundingClientRect().height
								};
							});

							// Create arrays of DOM elements
							var grouped = _.groupBy(heights, function(item) { return item.height; });
							for (var prop in grouped) {
								var rows = grouped[prop].map(function(item) { return item.row; });
								$(rows).css("height", prop + "px");
							}
						};
						self.updateRows = function(filterValues) {
							$scope.$broadcast("ngcCoinGridRow.update." + $scope.gridKey, filterValues);
						};

						self.applyFiltering = function(showResults, hideResults, filterValues) {
							// Apply the show first, since hide should take precedence
							$(Array.prototype.concat.apply([], showResults)).removeClass("hide");
							$(Array.prototype.concat.apply([], hideResults)).addClass("hide");

							// Fire off relevant events
							$scope.$emit("ngcCoinGridStickyHeader.reload." + $scope.gridKey);
							self.updateRows(filterValues);

							// Clear the results and set tracking vars
							firstFilter = false;
						};
						self.filterAll = function(newFilterValues) {
							var filterValues = newFilterValues || coinGridService.getFilters();
							var showResults = [], hideResults = [];

							// Update the row cache
							$pinnedRows = $pinnedTable.find("tr");
							$scrollableRows = $scrollableTable.find("tr");

							// Run filtering logic on table
							self.filterByGradeModifier(filterValues);
							if($scope.gridKey === "main")
								self.filterByDesignation(filterValues, showResults, hideResults);
							self.filterByGrade(filterValues, showResults, hideResults);
							self.toggleTotals(filterValues, showResults, hideResults);
							self.toggleUndated(filterValues, showResults, hideResults);
							self.filterByYear(filterValues, showResults, hideResults);

							self.applyFiltering(showResults, hideResults, filterValues);
						};
						self.filterByDesignation = function(filterValues, showResults, hideResults) {
							var apply = (!showResults && !hideResults);
							showResults = showResults || [];
							hideResults = hideResults || [];

							((filterValues.designation === "MS") ? showResults : hideResults).push($element.find(".ms").toArray());
							((filterValues.designation === "PF") ? showResults : hideResults).push($element.find(".pf").toArray());

							if(apply)
								self.applyFiltering(showResults, hideResults, filterValues);

							self.currentDesignation = filterValues.designation;

							if(self.currentView === "census" && $scope.gridKey === "main") {
								var focusedCell = self.getFocusedCell();
								if(!focusedCell || focusedCell.length == 0)
									return;

								self.toggleRequestGradeClassForHeader();
							}							
						};
						self.filterByGrade = function(filterValues, showResults, hideResults) {
							var apply = (showResults === undefined && hideResults === undefined);
							showResults = showResults || [];
							hideResults = hideResults || [];

							var fromGrade, toGrade;
							if(self.isDetailsCensus) {
								fromGrade = filterValues.fromGradeDetails;
								toGrade = filterValues.toGradeDetails;
							} else {
								fromGrade = filterValues.fromGrade;
								toGrade = filterValues.toGrade;
							}

							if(fromGrade && toGrade) {
								var headerIndexes = [0];
								if(self.isDetailsCensus)
									headerIndexes.push($scrollableRows.filter(".header-row").index());

								headerIndexes.forEach(function(headerIndex, index) {
									var headerRow = $($scrollableRows[headerIndex]);
									var start = headerRow.find(".grade-" + fromGrade + ":first").index();
									var end = headerRow.find(".grade-" + toGrade + ":last").index() + 1;
									var lastRowIndex = headerIndexes[index + 1] || $scrollableRows.length + 1;
									$scrollableRows.slice(headerIndex, lastRowIndex).each(function() {
										var cells = $(this).find("th, td");
										showResults.push(cells.slice(start, end).toArray());
										hideResults.push(cells.slice(0, start).toArray());
										hideResults.push(cells.slice(end, Number.MAX_VALUE).toArray());
									});
								});
							}

							if(apply)
								self.applyFiltering(showResults, hideResults, filterValues);
						};
						self.filterByGradeModifier = function(filterValues) {
							if(self.isDetailsCensus)
								return;

							styleService.clearStyles($scope.gridKey);
							gradeStyles = [];
							var showAllGrades = (filterValues.base && filterValues.star && filterValues.plus) ||
												(!filterValues.base && !filterValues.star && !filterValues.plus);

							if(showAllGrades)
								return;

							if(!filterValues.base && gradeStyles.indexOf(gridStyles.base) < 0)
								gradeStyles.push(gridStyles.base, headerStyles.base);
							if(!filterValues.plus && gradeStyles.indexOf(gridStyles.plus) < 0)
								gradeStyles.push(gridStyles.plus, headerStyles.plus);
							if(!filterValues.star && gradeStyles.indexOf(gridStyles.star) < 0)
								gradeStyles.push(gridStyles.star, headerStyles.star);
							if(!(filterValues.plus && filterValues.star) && gradeStyles.indexOf(gridStyles.plusStar) < 0)
								gradeStyles.push(gridStyles.plusStar, headerStyles.plusStar);

							if(gradeStyles.length > 0)
								styleService.addStyles($scope.gridKey, gradeStyles);
						};
						self.filterByYear = function(filterValues, showResults, hideResults) {
							var apply = (!showResults && !hideResults);
							showResults = showResults || [];
							hideResults = hideResults || [];

							if($scope.useYearFilters && filterValues.fromYear && filterValues.toYear) {
								var fromYear = parseInt(filterValues.fromYear.year);
								var toYear = parseInt(filterValues.toYear.year);

								var visibleSelectors = [];
								for(var year = fromYear; year <= toYear; year++) {
									visibleSelectors.push("." + year);
								}

								[$pinnedRows, $scrollableRows].forEach(function($rows) {
									var targetRows = $rows.not(".total, .header-row, .undated");
									var visibleRows = targetRows.filter("." + filterValues.designation.toLowerCase())
																.filter(visibleSelectors.join(","));
									var hiddenRows = targetRows.not(visibleRows);

									showResults.push(visibleRows.toArray());
									hideResults.push(hiddenRows.toArray());
								});

								if(apply)
									self.applyFiltering(showResults, hideResults, filterValues);
							}
						};
						self.toggleRequestGradeClassForHeader = function() {
							var gradeClass = ".grade-" + params["grade"];
							if(params["grade-des"]) {
								gradeClass = "." + params["grade-des"] + cellClass;
							} else {
								gradeClass = ".base" + gradeClass;
							}

							//set the request-grade class for header here as there is no easy way to do it on server side
							var gradeInHeader = $element.find(".header-cell" + gradeClass);
							if(gradeInHeader.length === 0)
								return;

							if(($scope.gridKey == "main" && self.currentDesignation && params["des"] && params["des"].toLowerCase() === self.currentDesignation.toLowerCase()) || $scope.gridKey !== "main")
								gradeInHeader.addClass("request-grade");
							else
								gradeInHeader.removeClass("request-grade");
						};
						self.toggleTotals = function(filterValues, showResults, hideResults) {
							var apply = (!showResults && !hideResults);
							showResults = showResults || [];
							hideResults = hideResults || [];

							if(filterValues.total) {
								(filterValues.designation === "MS" ? showResults : hideResults).push($element.find(".ms.total").toArray());
								(filterValues.designation === "PF" ? showResults : hideResults).push($element.find(".pf.total").toArray());
								(filterValues.designation === "SP" ? showResults : hideResults).push($element.find(".sp.total").toArray());
								(filterValues.designation === "PL" ? showResults : hideResults).push($element.find(".pl.total").toArray());
							} else {
								hideResults.push($element.find("tr.total").toArray());
							}

							if(apply)
								self.applyFiltering(showResults, hideResults, filterValues);
						};
						self.toggleUndated = function(filterValues, showResults, hideResults) {
							var apply = (!showResults && !hideResults);
							showResults = showResults || [];
							hideResults = hideResults || [];

							if(filterValues.undated) {
								showResults.push($element.find("tr.undated").toArray());
							} else {
								hideResults.push($element.find("tr.undated").toArray());
							}

							if(apply)
								self.applyFiltering(showResults, hideResults, filterValues);
						};

						self.resizeScrollableTable = function() {
							$scrollableContainer.height($pinnedContainer[0].getBoundingClientRect().height);
						};
						self.switchView = function(view) {
							self.currentView = view || self.currentView;
							switch (self.currentView) {
								case "census":
									$element.find(".price-guide-only").addClass("hide");
									$element.find(".census-only").removeClass("hide");
									break;
								case "price-guide":
									$element.find(".census-only").addClass("hide");
									$element.find(".price-guide-only").removeClass("hide");
									break;
							}

							$element.removeClass(_.without(views, self.currentView).join(" "));

							// TODO: Temporary workaround for CCG-21397, need to find a better way to pull the details census grid up
							if($scope.gridKey !== "varieties")
								$element.addClass(self.currentView);
						};
						self.scrollToHighlightedRow = function() {
							var highlightedRow = $element.find(".row-highlight");
							if(highlightedRow.length === 0)
								return;

							var container, overrideOffset;
							if($scope.gridKey !== "main") {
								container = $(".offcanvas-pane-details");
								overrideOffset = true;
							}

							ngcScrollService.performScroll(highlightedRow, 250, container, -200, overrideOffset);
						};
						self.scrollToDefaultGrade = function() {
							if(self.currentView === "census" && params["grade"]) {							
								var focusedCell = self.getFocusedCell();
								if(!focusedCell || focusedCell.length == 0)
									return;

								self.toggleRequestGradeClassForHeader();
								self.scrollToCell(focusedCell);							
							} else {
								$scrollableContainer.scrollLeft($scrollableTable.width());
							}
						};
						self.getFocusedCell = function() {
							var highlightedRow = $element.find(".row-highlight");
							if(highlightedRow.length === 0)
								return null;

							return highlightedRow.find(".focused");
						}
						self.scrollToCell = function(cell) {
							var elementOffset = ngcWindowService.getElementOffset(cell);

							//the elementOffset is different in main grid and overlay, have to treat them differently
							if($scope.gridKey === "main") {
								if(elementOffset.left < 0)
									$scrollableContainer.scrollLeft($scrollableContainer.width() - Math.abs(elementOffset.left));
								else
									$scrollableContainer.scrollLeft($scrollableContainer.width() + elementOffset.left);
							} else {
								//elementOffset is never negative for overlay
								$scrollableContainer.scrollLeft(elementOffset.left - $scrollableContainer.width() - 1500);
							}
						};
						self.load = function() {
							$pinnedContainer	 = $element.find(".pinned");
							$pinnedTable		 = $pinnedContainer.find("table");
							$pinnedHeader        = $pinnedTable.find("tr.header-row:first-child");
							$pinnedRows          = $pinnedTable.find("tr");
							$scrollableContainer = $element.find(".scrollable");
							$scrollableTable     = $scrollableContainer.find("table");
							$scrollableHeader    = $scrollableTable.find("tr.header-row:first-child");
							$scrollableRows      = $scrollableTable.find("tr");
							$scrollableCols      = $scrollableTable.find("colgroup");
						};
					}
				],
				link: function(scope, elem, attrs, ctrl, transclude) {
					transclude(scope, function(clone, transScope) {
						// Only compile the pinned table
						$compile(clone.find(".pinned"))(transScope);
						elem.append(clone);
					});

					ctrl.load();
					ctrl.switchView();
					ctrl.setRowStyles();
					ctrl.setRowHeights();

					if(scope.gridKey === "main")
						ctrl.scrollToDefaultGrade();

					$interval(function() {
						ctrl.scrollToHighlightedRow();
						if(scope.gridKey !== "main")
							ctrl.scrollToDefaultGrade();
					}, 0, 1);

					$rootScope.$on("ngcCoinGridFilter.applyFilters." + scope.gridKey, function(e, filterValues) {
						ctrl.filterAll(filterValues);
						ctrl.setRowStyles();
						ctrl.setRowHeights();
					});
					$rootScope.$on("ngcCoinGridFilter.filterByDesignation." + scope.gridKey, function(e, filterValues) {
						ctrl.filterByDesignation(filterValues);
						ctrl.toggleTotals(filterValues);
						ctrl.setRowStyles();
						ctrl.setRowHeights();
					});
					$rootScope.$on("ngcCoinGridFilter.filterByGrade." + scope.gridKey, function(e, filterValues) {
						ctrl.filterByGrade(filterValues);
					});
					$rootScope.$on("ngcCoinGridFilter.filterByGradeModifier." + scope.gridKey, function(e, filterValues) {
						ctrl.filterByGradeModifier(filterValues);
						ctrl.filterByGrade(filterValues);
						ctrl.updateRows(filterValues);
					});
					$rootScope.$on("ngcCoinGridFilter.filterByYear." + scope.gridKey, function(e, filterValues) {
						ctrl.filterByYear(filterValues);
						ctrl.setRowStyles();
						ctrl.setRowHeights();
					});
					$rootScope.$on("ngcCoinGridFilter.toggleTotals." + scope.gridKey, function(e, filterValues) {
						ctrl.toggleTotals(filterValues);
						ctrl.setRowStyles();
					});
					$rootScope.$on("ngcCoinGridFilter.toggleUndated." + scope.gridKey, function(e, filterValues) {
						ctrl.toggleUndated(filterValues);
						ctrl.setRowStyles();
						ctrl.setRowHeights();
					});
					scope.$on("ngcCoinGridDataView.viewSwitched." + scope.gridKey, function(e, view, skipScopeApply, scrollToDefaultGrade) {
						// Since the content is transcluded, we need to call $apply before
						// setting heights so the pinned table will already have been updated
						if(!skipScopeApply)
							scope.$apply();

						ctrl.switchView(view);
						ctrl.load();

						// Call the controlling filter so we pick up the non-persistent fields like year
						$rootScope.$broadcast("ngcCoinGridFilter.changeView." + scope.gridKey, view);

						ctrl.setRowStyles();
						ctrl.setRowHeights();

						if(scrollToDefaultGrade)
							ctrl.scrollToDefaultGrade();
					});
					scope.$on("ngcCoinGrid.scrollToHighlightedRow." + scope.gridKey, function(e, view) {
						ctrl.scrollToHighlightedRow();
					});

					// Call row height method again to fix any stragglers
					windowWatcher.onResize(ctrl.setRowHeights);
					angular.element($document).ready(ctrl.setRowHeights);
				}
			};
		}]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcCoinViewMenu", [
		"$rootScope", "$http", "$window", "$compile", "coinGridService", "urlService",
		function($rootScope, $http, $window, $compile, coinGridService, urlService) {
			return {
				link: function (scope, elem, attrs) {
					var loading = null;
					var publicUrl = null;
					var detailsUrl = null;
					var varietyUrl = null;
					var varietyGridUrl = null;
					var detailsGridUrl = null;
					var key = attrs.ngcCoinViewMenu;
					var parentCoinId = attrs.parentCoinId;
					var subcategoryId = attrs.subcategoryId;

					updateUrls();
					var currentUrl = urlService.path();
					if (currentUrl.indexOf(varietyUrl.split("?")[0]) >= 0)
						showVarieties(null, true);
					else if (currentUrl.indexOf(detailsUrl.split("?")[0]) >= 0)
						showDetails(true);

					elem.on("click", "span.icon-ngc-details", function(e) { showDetails(e); });
					elem.on("click", "span.icon-ngc-variety", function(e) { showVarieties(e); });

					function showVarieties(e, skipUrlChange) {
						updateUrls();
						toggleLoadingVariety(e, true);
							$http.get(varietyGridUrl)
								.then(function(response) {
									toggleLoadingVariety(e, false);
									if(!skipUrlChange)
										urlService.replaceUrl(publicUrl + varietyUrl);
									$rootScope.$broadcast("ccgOverlay.open.varieties", response.data, function() {
										urlService.replaceUrl(publicUrl);
									});
								});
					}
					function showDetails(skipUrlChange) {
						updateUrls();
						toggleLoading(true);
							$http.get(detailsGridUrl)
							.then(function(response) {
								toggleLoading(false);
								urlService.replaceUrl(publicUrl + detailsUrl);
								$rootScope.$broadcast("ccgOverlay.open.details", response.data, function() {
									urlService.replaceUrl(publicUrl);
								});
							});
					}

					function toggleLoading(isLoading) {
						if (isLoading === undefined)
							return;

						if (isLoading) {
							loading = $compile('<span ngc-loading-overlay visible="true"></span>')(scope);
							loading.appendTo(elem.find(".types:visible"));
							isLoading = true;
						} else {
							isLoading = false;
							loading.remove();
						}
					}

					function toggleLoadingVariety(e, isLoading) {
						if (isLoading === undefined || e == null)
							return;

						if (isLoading) {
							//remove the current spinner if there is one.
							var currentSpinner = elem.find(".loading:first");
							if(currentSpinner)
								currentSpinner.remove();

							loading = $compile('<span class="loading" visible="true"></span>')(scope);
							loading.appendTo(e.currentTarget);
							isLoading = true;
						} else {
							isLoading = false;
							loading.remove();
						}
					}

					function updateUrls() {
						publicUrl = urlService.path();

						if (publicUrl.indexOf("varieties/") >= 0)
							publicUrl = publicUrl.substr(0, publicUrl.indexOf("varieties/"));
						else if (publicUrl.indexOf("details/") >= 0)
							publicUrl = publicUrl.substr(0, publicUrl.indexOf("details/"));

						var category = publicUrl.indexOf("united-states") >= 0 || publicUrl.indexOf("us-tokens-and-medals") >= 0 ? "us" : "world";
						detailsUrl = "details/" + parentCoinId + "/" + $window.location.search;
						varietyUrl = "varieties/" + parentCoinId + "/" + $window.location.search;

						detailsGridUrl = coinGridService.formatOverlayUrl({
							type: "/" + publicUrl.split("/")[1] + "/",
							category: category,
							subcatId: subcategoryId,
							partialUrl: detailsUrl,
						});
						varietyGridUrl = coinGridService.formatOverlayUrl({
							type: "/" + publicUrl.split("/")[1] + "/",
							category: category,
							subcatId: subcategoryId,
							partialUrl: varietyUrl,
						});
					}

				}
			}
		}
	]);;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcPriceHistoryGrid", [
		"$compile", "urlService", "ngcPriceHistoryService", 
		function($compile, urlService, ngcPriceHistoryService) {
			return {
				link: function(scope, element, attrs) {
					function openPriceHistory(coinID, grade, labelCoinID) {
						$(".fancybox-slide").prepend("<div class='loading'></div>");
						ngcPriceHistoryService.getPriceHistoryOverlay(coinID, grade, labelCoinID)
							.then(function(data) {
								if(!attrs.skipUrlUpdate)
									urlAddHistory(coinID, grade, labelCoinID);

								var template = angular.element(data);
								var compiledTemplate = $compile(template);
								compiledTemplate(scope);

								var options = {
									type: 'html',
									closeSpeed: 0,
									touch: {
										vertical : false,
										momentum : false
									},
									beforeShow: function() {
										$("div").remove(".loading");
									},
									afterClose: function() {
										if(!attrs.skipUrlUpdate)
											urlRemoveHistory();
									}
								};
								$.fancybox.open(template, options);
								return true;
							});
					}
					function urlRemoveHistory() {
						var uri = new URI();
						var args = uri.path().split("/");
						var historyIndex = _.findIndex(args, function(arg) { return arg === "history"; });

						if(historyIndex > 0) {
							var newPath = args.slice(0, historyIndex).join("/") + "/";
							urlService.replaceUrl(newPath);
						}
					}
					function urlAddHistory(coinID, grade, labelCoinID) {
						var uri = new URI();
						var args = uri.path().split("/");
						var historyIndex = _.findIndex(args, function(arg) { return arg === "history"; });
						if(historyIndex < 0) {
							var path = urlService.path();
							if(_.last(path) !== "/")
								path = path + "/";
							
							var newPathItems = ["history", coinID, grade.replace("+","plus")];
							path = path + newPathItems.join("/") + "/";

							if(labelCoinID)
								path = path + "?lc=" + labelCoinID;

							urlService.replaceUrl(path);
						}
					}
					if(!attrs.skipUrlUpdate) {
						var uri				= new URI();
						var args			= uri.path().split("/");
						var labelCoinID		= uri.search(true)["lc"];
						var historyIndex	= _.findIndex(args, function(arg) { return arg === "history"; });
						if(historyIndex > 0)
						{
							if((attrs.isVariety === "true") === (args[historyIndex - 2] === "varieties"))
							{
								var coinID = args[historyIndex + 1];
								var grade = args[historyIndex + 2];
								openPriceHistory(coinID, grade, labelCoinID);
							}
						}
					}

					$(element).on("click", "[price-history-link]", function() {
						var coinID = $(this).attr("coin-id");
						var grade = $(this).attr("grade");
						var labelCoinID = $(this).attr("label-coin-id");

						if(coinID === undefined || grade === undefined)
							return;

						openPriceHistory(coinID, grade, labelCoinID);
					});
				}
			};
		}]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcCertLookupSearch", [
		"$window",
		function($window) {
			return {
				controllerAs: "$ctrl",
				controller: [
					"$scope", "certLookupService",
					function($scope, certLookupService) {
						var $ctrl = this;

						$ctrl.lookup = function(certNumber, grade) {
							if(!$ctrl.certNumber || !$ctrl.grade)
								return;

							$ctrl.errorMessage = "";
							if($ctrl.isHomepage) {
								redirectToCert(certNumber, grade);
							} else {
								$scope.form.$submitting = true;
								certLookupService.validateCert(certNumber, grade)
									.then(function(result) {
										if(result == "Success") {
											redirectToCert(certNumber, grade);
										} else {
											$ctrl.errorMessage = result;
											$scope.form.$submitting = false;
										}
									})
									.catch(function(error) {
										$ctrl.errorMessage = "Server Down";
										$scope.form.$submitting = false;
									});
							}
						};

						function redirectToCert(certNumber, grade) {
							$window.location.href = "/certlookup/" + certNumber + "/" + grade + "/";
						};
					}
				],
				link: function(scope, elem, attrs, $ctrl) {
					$ctrl.isHomepage = ($window.location.pathname == "/");
					$ctrl.certNumber = attrs.certNumber;
					$ctrl.grade = attrs.grade;

					// If there is a cert number and grade, that means it is pre-filling with invalid values.
					$ctrl.noResults = (!$ctrl.isHomepage && $ctrl.certNumber && $ctrl.grade);
					if(attrs.proIvDown) {
						$ctrl.errorMessage = "ProV Down";
					} else if(attrs.contactSupport) {
						$ctrl.errorMessage = "Contact Support";
					} else if(attrs.rateLimited) {
						$ctrl.errorMessage = "Rate Limited";
					} else if($ctrl.certNumber && $ctrl.grade) {
						$ctrl.errorMessage = "No Match";
					}
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.service("certLookupService", [
		"$http",
		function($http) {
			return {
				validateCert: function(certNumber, grade) {
					return $http.get("/certlookup/data/" + certNumber + "/" + grade + "/")
						.then(function(response) {
							return response.data;
						});
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("aboutNGCController", [
		"continentGradingSummaryService",
		function(continentGradingSummaryService) {
			var $ctrl = this;

			var colors = [
				"#e8c72d",
				"#cd7528",
				"#c70000",
				"#0f76ce",
				"#abe835",
				"#003090",
			];

			continentGradingSummaryService.getSummary()
				.then(function(summary) {
					$ctrl.totalGraded = _.reduce(summary, function(previous, continent) {
						return previous + continent.TotalGraded + continent.DetailGraded;
					}, 0);

					$ctrl.displayTotal = $ctrl.totalGraded - $ctrl.totalGraded % 1000000;

					$ctrl.chartData = summary.map(function(continent, index) {
						var total = continent.TotalGraded + continent.DetailGraded;
						var percentage = (100 * total / $ctrl.totalGraded).toFixed(2);

						return {
							name: continent.Name,
							tooltip: continent.Name + ": " + percentage + "%",
							value: total,
							color: colors[index],
							hoverColor: colors[index],
						};
					});
					$ctrl.legendData = _.cloneDeep($ctrl.chartData)
					// move the last element (North America) to the beginning
					$ctrl.legendData.unshift($ctrl.legendData.pop());
				});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.service("continentGradingSummaryService", [
		"webServiceFactory",
		function(webServiceFactory) {
			return webServiceFactory.create({
				getSummary: function() {
					return this.get("/census/continents/");
				}
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("thirtyYearsCtrl", [
		"$scope",
		function($scope) {
			var duration = 1;
			var holders = [
				{ year: "1987", backgroundColor: "#6f7072" },
				{ year: "1987", backgroundColor: "#a4810d" },
				{ year: "1990", backgroundColor: "#a9eefd" },
				{ year: "1993", backgroundColor: "#0f76ce" },
				{ year: "1997", backgroundColor: "#6f7072" },
				{ year: "2001", backgroundColor: "#a4810d" },
				{ year: "2008", backgroundColor: "#a9eefd" },
				{ year: "2016", backgroundColor: "#0f76ce" }
			];

			$scope.page = 0;
			$scope.allowNext = true;
			$scope.prev = function() { $scope.goToPage($scope.page - 1); };
			$scope.next = function() { $scope.goToPage($scope.page + 1); };
			$scope.goToPage = function(newPage) {
				if(newPage === $scope.page || newPage < 0 || newPage >= holders.length) {
					return;
				}

				var direction = newPage - $scope.page;
				var slideFromPosition = (direction > 0) ? "400%" : "-400%";
				var slideToPosition = (direction > 0) ? "-400%" : "400%";
				var $currentSlide = $(".evolution-slide").get($scope.page);
				var $targetSlide = $(".evolution-slide").get(newPage);
				var newHolder = holders[newPage];

				gsap.to(".evolution-slider", duration, { backgroundColor: newHolder.backgroundColor })
				gsap.to(".year", duration, { text: { value: newHolder.year, delimiter: " " }});
				gsap.set($targetSlide, { left: slideFromPosition });
				gsap.to($targetSlide, duration, { left: "0%" });
				gsap.to($currentSlide, duration, { left: slideToPosition });

				$scope.page = newPage;
				$scope.allowPrevious = ($scope.page > 0);
				$scope.allowNext = ($scope.page < (holders.length - 1));
			}
		}
	]);

;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcBgImage", [
		"$q", "lazyLoadService",
		function($q, lazyLoadService) {
			return {
				scope: true,
				link: function(scope, element, attrs, controller) {
					if(attrs.notLazy === undefined)
						lazyLoadService.register(element, assignBackground);

					scope.$on("bgImage.refresh", assignBackground);

					function assignBackground() {
						var imageUrl = scope.$eval(attrs.ngcBgImage);
						preloadImage(imageUrl)
							.then(function(isImage) {
								element.toggleClass("has-image", isImage);
								element.toggleClass("no-image", !isImage);

								if(isImage)
									element.css("background-image", "url(" + imageUrl + ")");
								else element.removeAttr("style");
							});
					};

					function preloadImage(imageUrl) {
						var promise = $q.defer();
						if(!imageUrl) {
							promise.resolve(false);
						} else {
							$("<img>")
								.on("load", function() { promise.resolve(true); })
								.attr("src", imageUrl);
						}

						return promise.promise;
					};
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcBreadcrumbBar", [
		function() {
			return {
				template: "<div class='breadcrumb-bar'></div>",
				link: function(scope, elem, attrs) {
					var update = function(e, breadcrumbs) {
						// Clear existing breadcrumbs
						elem.empty();
				
						// Build new links
						_.forEach(breadcrumbs, function(breadcrumb, index) {
							var link = $("<a></a>");
							link.attr("href", breadcrumb.url);
							link.text(breadcrumb.text);
							if (breadcrumb.classes) {
								link.addClass(breadcrumb.classes);
							}
							
							elem.append(link);
							link.wrap("<div></div>");
						}); 
					};

					scope.$on("ngcBreadcrumbBar.update", update);
				}
			};
		}
	]);;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcElementVisibilityEvent", [
		"ngcScrollService", "windowWatcher",
		function(ngcScrollService, windowWatcher) {
			return {
				scope: {
					onVisible: "=",
					onNotVisible: "=",
					offset: "="
				},
				link: function (scope, elem, attrs, ctrl) {
					var isVisible = false;
					ngcScrollService.registerOnScroll("ngcScrollPointEvent", checkIsInView);
					scope.$on("ngcElementVisibilityEvent.check", checkIsInView);
					
					function checkIsInView() {
						var isNowVisible = windowWatcher.withinViewport(elem, { y: scope.offset });
						if (isNowVisible !== isVisible) {
							if (isNowVisible)
								scope.onVisible();
							else scope.onNotVisible();
						}

						isVisible = isNowVisible;
					}
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcLoadingOverlay", [
		function() {
			return {
				scope: {
					key: "=ngcLoadingOverlay",
					visible: "="
				},
				template: "<span ng-show='loadingCtrl.visible' class='grid-loading-icon ng-hide'></span>",
				replace: true,
				bindToController: true,
				controllerAs: "loadingCtrl",
				controller: function() { },
				link: function(scope, elem, attrs, ctrl) {
					scope.$on("ngcLoadingOverlay.toggle", function(e, show) {
						ctrl.visible = show;
					});
				}
			};
		}
	]);;
"use strict";
angular.module("NGC.Registry")
	.run(function() {
		var url = new URI();
		var referrer = url.search(true).referrer;

		switch(referrer) {
			case "pngdealers":
				$.cookie("referrer", "png", { path: "/" });
				break;
			default:
				break;
		}
	})
	.directive("ngcReferralLink", [
		function() {
			return {
				template: "<a ng-href='{{referrerUrl}}' ng-hide='!isVisible' target='_blank'><i>&lt;</i> {{text}}</a>",
				link: function(scope, elem, attrs) {
					switch($.cookie("referrer")) {
						case "png":
							scope.isVisible = true;
							scope.referrerUrl = "http://www.pngdealers.org";
							scope.text = "Return to PNGdealers.org";
							break;
						default:
							scope.isVisible = false;
							break;

					}
				}
			};
		}
	]);;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcResizedContent", [
		"windowWatcher",
		function(windowWatcher) {
			return {
				controller: [
					"$element", "$timeout",
					function($element, $timeout) {
						var self = this;
						self.resize = function() {
							var parent;
							if (self.parentSelector) {
								parent = $(self.parentSelector)[0];
							} else {
								parent = $element.parent()[0];
							}

							$timeout(function() {
								var parentHeight = parent.getBoundingClientRect().height;
								var position = $element.position();
								if (position.top > parentHeight)
									return;

								$element.css("max-height", parentHeight - position.top);
							});
						};
					}
				],
				link: function(scope, elem, attrs, ctrl) {
					windowWatcher.onResize(ctrl.resize);
					ctrl.parentSelector = attrs.ngcResizedContent;
					ctrl.resize();
				}
			};
		}
	]);;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcScrollPointEvent", [
		"ngcScrollService", "ngcWindowService", "$document",
		function(ngcScrollService, ngcWindowService, $document) {
			return {
				scope: {
					onTriggered: "=ngcScrollPointEvent",
					onRestored: "=",
					triggerOffset: "=",
					restoreOffset: "=",
					disableOnReady: "="
				},
				link: function(scope, elem, attrs, ctrl) {
					var isTriggered = false;
					ngcScrollService.registerOnScroll("ngcScrollPointEvent", handleScroll);

					scope.$on("$destroy", function() {
						ngcScrollService.deregisterOnScroll("ngcScrollPointEvent");
					});

					if (!scope.disableOnReady) {
						angular.element($document).ready(function() {
							handleScroll(ngcWindowService.getViewportScroll());
						});
					}

					function handleScroll(scroll) {
						if (!scroll || (isTriggered && !scope.onRestored)) {
							return;
						}

						var globalOffset = ngcScrollService.getGlobalOffset();
						var rect = elem[0].getBoundingClientRect();
						var triggerOffset = (scope.triggerOffset) ? parseFloat(scope.triggerOffset) : 0;
						var triggerY = rect.top + scroll.y + triggerOffset - globalOffset;
						var restoreOffset = (scope.restoreOffset) ? parseFloat(scope.restoreOffset) : 0;
						var restoreY = rect.top + scroll.y + restoreOffset - globalOffset;

						if (scroll.y >= triggerY && scroll.directionY === "down" && !isTriggered) {
							isTriggered = true;
							processEvent(scope.onTriggered);
						} else if (scroll.y < restoreY && scroll.directionY === "up" && isTriggered) {
							isTriggered = false;
							processEvent(scope.onRestored);
						}
					}

					function processEvent(callback, message) {
						var eventType = typeof(callback);
						switch (eventType) {
							case "string":
								scope.$emit(callback);
								break;
							case "function":
								callback();
								break;
						}
					}

				}
			};
		}
	]);;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcSelector", [
		function() {
			return {
				controller: [
					"$scope",
					function($scope) {
						$scope.selectedItem = null;
						$scope.events = $scope.events || {};
						angular.extend($scope.events, {
							toggle: function(item) {
								if($scope.events.isSelected(item))
									$scope.events.deselect(item);
								else $scope.events.select(item);
							},
							select: function(item) {
								$scope.selectedItem = item;
							},
							deselect: function(item) {
								$scope.selectedItem = null;
							},
							isSelected: function(item) {
								return ($scope.selectedItem === item);
							}
						});
					}
				],
				link: function(scope, element, attrs, controller) {
				}
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcStickyElement", [
		"$window", "ngcScrollService",
		function($window, ngcScrollService) {
			return {
				scope: {
					buffer: "="
				},
				link: function(scope, elem, attrs, ctrl) {
					var togglePoint, isFixed;
					var buffer = scope.buffer || 0;
					var clone = elem.clone()
						.css("opacity", 0)
						.addClass("hide");

					elem.after(clone);

					ngcScrollService.registerOnScroll(elem, handleScroll, true);
					scope.$on("$destroy", function() {
						ngcScrollService.deregisterOnScroll(elem);
						ngcScrollService.deregisterOffsetElement(elem);
					});

					function handleScroll(e) {
						var top = elem.offset().top;
						var offsetY = $window.pageYOffset;
						var toggled = false;
						if (offsetY > (top - buffer) && !isFixed) {
							togglePoint = top;
							elem.addClass("fixed").appendTo("body");
							clone.removeClass("hide");
							isFixed = true;
							toggled = true;
							ngcScrollService.registerOffsetElement(elem);
						} else if (offsetY < (togglePoint - buffer) && isFixed) {
							clone.addClass("hide").before(elem);
							elem.removeClass("fixed");
							isFixed = false;
							toggled = true;
							ngcScrollService.deregisterOffsetElement(elem);
						}

						if (toggled)
							scope.$emit("ngcStickyElement.toggled", isFixed);
					}
				}
			};
		}
	]);;
"use strict";
angular.module("NGC.Registry")
	.directive("superslides", [
		function() {
			return {
				link: function(scope, element, attrs, ctrl) {
					if(!element.superslides)
						return;

					element.superslides({
						animation		: attrs.animation,
						animation_speed	: attrs.animationSpeed,
						pagination		: attrs.pagination !== "false",
						play			: attrs.play
					});

					scope.$on("modal.visibilityChanged", function(e, isVisible) {
						if(isVisible)
							element.superslides("stop");
						else element.superslides("start");
					});
				}
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcDonutChart", [
		"urlService", "windowWatcher",
		function(urlService, windowWatcher) {
			return {
				scope: {
					chartData: "=chartData"
				},
				template: "<canvas></canvas>",
				bindToController: true,
				controllerAs: "chartCtrl",
				controller: [
					"$element", "$timeout",
					function($element, $timeout) {
						var $ctrl = this;

						$ctrl.context = $element.find("canvas")[0];
						$ctrl.chartLoaded = false;

						$ctrl.createChart = function() {
							new Chart($ctrl.context, {
								type: "doughnut",
								data: {
									labels: _.map($ctrl.chartData, "name"),
									datasets: [{
										data: _.map($ctrl.chartData, "value"),
										backgroundColor: _.map($ctrl.chartData, "color"),
									}]
								},
								options: {
									legend: {
										display: false
									},
									tooltips: {
										enabled: true,
										mode: "single",
										callbacks: {
											label: function(tooltipItems, data) {
												return $ctrl.chartData[tooltipItems.index].tooltip;
											}
										}
									}
								}
							});
						};

						windowWatcher.onScroll(checkVisibility, 150);
						$timeout(checkVisibility);

						function checkVisibility(parameters) {
							if(!$ctrl.chartLoaded) {
								if(windowWatcher.partiallyWithinViewport($element, 0.75)) {
									$ctrl.chartLoaded = true;
									$ctrl.createChart();
								}
							}
						}
					}
				]
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcDetailsGroup", [
		// None
		function() {
			return {
				scope: true,
				require: "^?ngcDetailsGroup",
				controllerAs: "detailsGroup",
				controller: [
					"$scope", "$element", "$timeout",
					function($scope, $element, $timeout) {
						var $this = this;
						this.details = {};
						this.shouldExpand = true;

						this.update = function(itemName, isOpen) {
							$this.details[itemName] = isOpen;
							$this.shouldExpand 		= !_.some($this.details);
						};
						this.updateAll = function(openAll) {
							$this.details		= _.mapValues($this.details, function() { return openAll; });
							$this.shouldExpand	= !openAll;
						};
						this.toggleAll = function() {
							if($this.shouldExpand)
								$element.find("details").attr("open", "");
							else $element.find("details").removeAttr("open");

							$this.updateAll($this.shouldExpand);
							$scope.$emit("ngcGridItem.relayout");
						};
					}
				]
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("details", [
		"$timeout",
		function($timeout) {
			return {
				restrict: "E",
				require: "^?ngcDetailsGroup",
				link: function(scope, element, attrs, ctrl) {
					if(!ctrl)
						return;

					var itemName = attrs.name;
					ctrl.update(itemName, false);
					element.on("click", "summary", function(e) {
						$timeout(function() {
							var isOpen = (element.attr("open") !== undefined);
							ctrl.update(itemName, isOpen);
						});
					});
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("gridDetailService", [
		"$rootScope",
		function($rootScope) {
			return {
				element: null,
				content: null,
				open: function(element, content) {
					this.element = element;
					this.content = content;
					$rootScope.$broadcast("ngcGridDetail.open", this.element, this.content);
				},
				close: function(element) {
					this.element = null;
					this.content = null;
					$rootScope.$broadcast("ngcGridDetail.close");
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcGridItem", [
		"$compile", "$timeout", "$q", "urlService",
		function($compile, $timeout, $q, urlService) {
			return {
				scope: true,
				transclude: true,
				templateUrl: urlService.root + "resources/scripts/components/grid/grid-item-template.html",
				controllerAs: "gridItem",
				controller: [
					"$scope", "domService", "windowWatcher", "partialsService",
					function($scope, dom, windowWatcher, partials) {
						var animateDuration = 350;
						this.selected = false;

						this.toggle = function(e) {
							if(!urlService.supportsHtml5History && this.publicUrl.indexOf("#") === -1)
								return;

							// Prevent default behaviour
							if(e) e.preventDefault();

							if(urlService.fullPath().indexOf(this.publicUrl) >= 0 || (this.skipUpdateUrl && this.selected))
								this.close(this.skipUpdateUrl);
							else this.open();
						}.bind(this);
						this.close = function(skipChangeUrl) {
							if(!skipChangeUrl)
								urlService.updateUrl(this.baseUrl || urlService.baseUrl);

							this.collapse(true);
							this.grid.selectedItem = null;
						}.bind(this);
						this.open = function(isFirstRun) {
							var promise	= $q.when();
							var loadNewContent = (this.$detailContent.children().length === 0);
							if(loadNewContent || (!this.grid.disableCache && !isFirstRun)) {
								this.isLoading = true;
								promise = partials.fetch(this.partialsUrl, this.grid.disableCache);
							}

							promise.then(function(content) {
								if(loadNewContent)
									this.setContent(content);

								var previous = this.grid.selectedItem;
								if(previous)
									previous.collapse(true);

								var stateObj = null;
								if(this.closeOnBack) {
									var backListener = $scope.$on("ngcGridItem.closeOnBack." + this.publicUrl, function() {
										this.close(true);

										// Calling the returned listener will deregister it
										backListener();
									}.bind(this));

									stateObj = { eventKeys: ["ngcGridItem.closeOnBack." + this.publicUrl] };
								}

								if(!this.skipUpdateUrl)
									urlService.updateWithState((this.isPageLoad) ? null : this.publicUrl, null, stateObj);
								this.isPageLoad = false;
								this.recenter(previous, !isFirstRun);
								this.expand(previous, !isFirstRun);
								this.grid.selectedItem = this;
							}.bind(this));
							promise["finally"](function() {
								this.isLoading = false;
							}.bind(this));
						}.bind(this);

						// == Content Setting and Measurement =========================================
						// ============================================================================
						this.setContent = function(content) {
							content = content.replace(/<(\/?):/g, "<$1");
							this.$detailContent.html(content);
							$compile(this.$detailContent.contents())($scope);
						}.bind(this);
						this.measureElements = function() {
							this.itemHeight		= Math.floor(this.$itemContent.outerHeight(true));
							this.detailHeight	= Math.floor(this.$detailContent[0].scrollHeight);
							this.totalHeight	= this.itemHeight + this.detailHeight;
						}.bind(this);
						this.resetElements = function() {
							this.$element
								.add(this.$detail)
								.removeClass("animate-height")
								.height("");

							this.$detail.detach();
						}.bind(this);
						this.isSameRow = function(other) {
							this.offset = this.$element.offset().top;

							if(other) {
								other.offset = other.$element.offset().top;
								return this.offset === other.offset;
							}
							else return false;
						}.bind(this);
						this.findOffscreenElements = function() {
							var firstOffscreen = false;
							windowWatcher.scrollWatcher.refresh();
							return this.$element
										.nextAll(".grid-item") // Only check elements after this since they will not effect scroll
										.filter(function() {
											if(firstOffscreen)
												return true;

											if(!windowWatcher.withinViewport(this, 2)) {
												firstOffscreen = true;
												return true;
											}
										});
						}.bind(this);

						// == Expansion and Collapsing ================================================
						// ============================================================================
						this.expand = function(previous, animated, hideOffscreen) {
							var $offscreen = (hideOffscreen !== false) ? this.findOffscreenElements() : $();
							$offscreen.css("visibility", "hidden");

							this.$element.append(this.$detail);

							$timeout(function() {
								var startTotalHeight, startDetailHeight;

								if(previous || this.selected) {
									var baseElement = (previous || this);
									if(this.isSameRow(baseElement)) {
										startTotalHeight = baseElement.totalHeight;
										startDetailHeight = baseElement.detailHeight;
									}
								}

								this.measureElements();
								this.resize(this.$element, animated, this.totalHeight, startTotalHeight || this.itemHeight);
								this.resize(this.$detail, animated, this.detailHeight, startDetailHeight || 0);
								$offscreen.css("visibility", "");
							}.bind(this));

							this.selected = true;
						}.bind(this);
						this.collapse = function(animated) {
							if(dom.isTouch || !windowWatcher.withinViewport(this.$detail))
								animated = false;

							this.resize(this.$element, animated, this.itemHeight, undefined, this.resetElements);
							this.resize(this.$detail, animated, 0, undefined, this.resetElements);
							this.selected = false;
						}.bind(this);

						// == Animations ==============================================================
						// ============================================================================
						this.resize = function($target, animated, toHeight, fromHeight, onComplete) {
							if(!$target.is(":visible"))
								return;

							if(fromHeight !== undefined) {
								$target.removeClass("animate-height");
								$target.height(fromHeight);
							}

							$timeout(function() {
								$target.toggleClass("animate-height", animated);
								$target.height(toHeight);

								if(onComplete) {
									$timeout(function() {
										onComplete();
									}, animated ? animateDuration : 0);
								}

								$timeout(function() {
									$scope.$emit("DOMPosition.needsRefresh");
								}.bind(this), animated ? animateDuration : 0);
							}.bind(this));
						}.bind(this);
						this.recenter = function(previous, animated) {
							// If we're not changing rows, don't do anything.
							if(this.isSameRow(previous))
								return;

							if(previous && this.offset > previous.offset)
								this.offset -= previous.detailHeight;

							if(animated && !dom.isTouch) {
								this.$body.velocity("scroll", {
									duration: 500,
									offset	: this.offset,
									queue	: false
								});
							}
							else window.scrollTo(0, this.offset);
						}.bind(this);
					}
				],
				require: [ "ngcGridItem", "^ngcGrid" ],
				link: function(scope, element, attrs, controllers, transclude) {
					var ctrl 			= controllers[0];
					ctrl.$body			= $("html");
					ctrl.$element		= element;
					ctrl.$itemContent	= element.find(".grid-item-content");
					ctrl.$detail		= element.find(".grid-detail");
					ctrl.$detailContent	= element.find(".grid-detail-content");
					ctrl.$itemContent.append(transclude(scope, function() {}));
					ctrl.grid			= controllers[1];
					ctrl.publicUrl		= encodeURI(attrs.href).toLowerCase();
					ctrl.baseUrl		= attrs.baseUrl;
					ctrl.partialsUrl	= attrs.ngcGridItem;
					ctrl.closeOnBack	= attrs.closeOnBack !== undefined;
					ctrl.skipUpdateUrl	= attrs.skipUpdateUrl !== undefined;
					ctrl.grid.register(ctrl);
					ctrl.resetElements();

					if(attrs.openActionElement) {
						ctrl.openActionElement = element.find(attrs.openActionElement);
					}
					// If we start on this item's url, trigger the toggle
					if(urlService.fullPath().toLowerCase().indexOf(ctrl.publicUrl) >= 0) {
						ctrl.isPageLoad = true;
						$(document).ready(function() {
							if(ctrl.openActionElement) {
								$timeout(function() {
									angular.element(ctrl.openActionElement).triggerHandler("click");
								}, 0);
							} else {
								ctrl.open(true);
							}
						});
					}
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcGrid", [
		"windowWatcher",
		function(windowWatcher) {
			return {
				controllerAs: "grid",
				controller: [
					"$element",
					function($element) {
						this.gridItems = [];
						this.selectedItem = null;
						this.disableCache = false;

						this.register = function(gridItem) {
							this.gridItems.push(gridItem);
						}
						this.relayout = function() {
							if(!this.selectedItem)
								return;

							this.selectedItem.expand(null, null, false);
						}.bind(this);
					}
				],
				link: function(scope, element, attrs, ctrl) {
					ctrl.hashedUrls		= (attrs.hashedUrls !== undefined);
					ctrl.disableCache	= (attrs.disableCache !== undefined);

					scope.$on("ngcGridItem.relayout", ctrl.relayout);
					scope.$on("ccgViewSwitch.viewSwitched", ctrl.relayout);
					windowWatcher.onResize(ctrl.relayout);
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("notifier", [
		// No dependencies
		function() {
			var notification = humane.create();

			function notifyUser(message, timeout, additionalClass) {
				if(!message)
					return;

				notification.log(message, {
					timeout: timeout || humane.timeout,
					clickToClose: true,
					addnCls: additionalClass
				});
			}

			return {
				showError: function(message, timeout) {
					notifyUser(message, timeout, "error");
				},
				showMessage: function(message, timeout) {
					notifyUser(message, timeout);
				},
				clearNotifications: function() {
					// Clear the queue
					notification.queue = [];

					// Remove the current notification
					if(notification.currentTimer)
						notification.remove();
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.value("coinGrades", {
		"census": ["PrAg", "G", "VG", "F", "VF", "40", "45", "50", "53", "55", "58", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70"],
		"price-guide": ["PrAg", "G", "VG", "F", "VF", "XF", "50", "53", "55", "58", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70"],
		"details-census": ["PrAg", "G", "VG", "F", "VF", "XF", "AU", "UNC"],
		"pcgs": ["Genuine", "PrAg Details", "G Details", "VG Details", "F Details", "VF Details", "XF Details", "AU Details", "UNC Details", "Proof Details",
			     "1", "2", "3", "4", "6", "8", "10", "12", "15", "20", "25", "30", "35", "40", "45", "45+", "50", "50+", "53", "53+", "55", "55+", "58","58+",
			     "60", "60+", "61", "61+", "62", "62+", "63", "63+", "64", "64+", "65", "65+", "66", "66+", "67", "67+", "68", "68+", "69", "70"],
		"set-scores": [
			{ code: "PrAg",	 grade: "PrAg" },
			{ code: "G",	 grade: "G" },
			{ code: "VG",	 grade: "VG" },
			{ code: "F",	 grade: "F" },
			{ code: "VF",	 grade: "VF" },
			{ code: "40",	 grade: "40" },
			{ code: "45",	 grade: "45" },
			{ code: "50",	 grade: "50" },
			{ code: "53",	 grade: "53" },
			{ code: "55",	 grade: "55" },
			{ code: "58",	 grade: "58" },
			{ code: "60",	 grade: "60" },
			{ code: "61",	 grade: "61" },
			{ code: "62",	 grade: "62" },
			{ code: "63",	 grade: "63" },
			{ code: "64",	 grade: "64" },
			{ code: "65",	 grade: "65" },
			{ code: "66",	 grade: "66" },
			{ code: "67",	 grade: "67" },
			{ code: "68",	 grade: "68" },
			{ code: "69",	 grade: "69" },
			{ code: "70",	 grade: "70" },
		]
	});

;
"use strict";
angular.module("NGC.Registry")
	.directive("summary", [
		"$timeout",
		function($timeout) {
			return {
				restrict: "E",
				link: function(scope, element, attrs, ctrl) {
					var $details = element.parent();
					if(!$details.is("details"))
						return;

					// Call in timeout so angular has a chance to bind items
					// before determining if this is the only child
					$timeout(function() {
						var onlyChild = $details.siblings("details").length === 0;
						$details.toggleClass("only-child", onlyChild);

						if (Modernizr.details && onlyChild) {
							// If the details element is all by itself, and the browser supports
							// the details element, prevent the native behavior (collapse)
							$details.prop("open", onlyChild);
							element.on("click", function(e) {
								e.preventDefault();
							});
						} else if (!Modernizr.details && !onlyChild) {
							// Otherwise, toggle the open attribute by hand
							element.on("click", function(e) {
								if ($details.attr("open"))
									$details.removeAttr("open");
								else $details.attr("open", "true");
							});
						}
					});

					element.on("click", function(e) {
						$timeout(function() {
							scope.$emit("ngcGridItem.relayout");
						});
					});
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcPriceHistoryOverlay", [
		"ngcPriceHistoryService", "windowWatcher",
		function(ngcPriceHistoryService, windowWatcher) {
			return {
				controller: [function() {}],
				controllerAs: "$priceHistoryOverlayCtrl",
				link: function(scope, elem, attrs, ctrl) {
					ngcPriceHistoryService.getPriceHistory(attrs.coinId, attrs.grade, attrs.labelCoinId)
						.then(function(result) {
							ctrl.coin			= result.coin;
							ctrl.priceChange	= result.priceChange;
							ctrl.displayGrade	= result.displayGrade;
							ctrl.labelName		= result.labelName;
							ctrl.showGraph		= result.prices.length > 1;

							// this is either 1,111.1 or 1.111,1
							// removing all the 1's gives us the thousand separator followed by the decimal point.
							var separators = (1111.1).toLocaleString(CCG.culture).split('1').join('');

							Highcharts.setOptions({
								lang: {
									rangeSelectorFrom: attrs.localizeFrom,
									rangeSelectorTo: attrs.localizeTo,
									thousandsSep: separators[0],
									decimalPoint: separators[1],
									rangeSelectorZoom: ''
								},
								xAxis: {
									ordinal: false
								}
							});

							function createPrice(x) {
								var decimals = 2;
								if(Math.abs(x) > 100)
									decimals = 0;
										
								return Highcharts.numberFormat(x, decimals);
							}
							ctrl.price = '$' + createPrice(_.last(result.prices)[1]);
							ctrl.priceChangeActual = '$' + createPrice(result.priceChangeActual);

							var chart = Highcharts.stockChart('graph', {
								rangeSelector: {
									selected: 3,
									buttons: [{
											type: 'ytd',
											text: attrs.localizeYtd
										}, {
											type: 'year',
											count: 1,
											text: attrs.localize1y
										}, {
											type: 'year',
											count: 3,
											text: attrs.localize3y
										}, {
											type: 'year',
											count: 5,
											text: attrs.localize5y
										}, {
											type: 'all',
											text: attrs.localizeAll
										}],
									inputEditDateFormat: '%m-%d-%Y'
								},
								tooltip: {
									pointFormatter: function() {
										return attrs.localizePrice + ' ($): <b>' + createPrice(this.y) + '</b>';
									},
									xDateFormat: '%m-%d-%Y'
								},
								series: [{
									name: attrs.localizePrice + ' ($)',
									data: result.prices,
									tooltip: {
										valueDecimals: 2
									}
								}],
								yAxis: {
									min : 0
								},
								credits: {
									enabled: false
								}
							});
							setTimeout(chart.reflow.bind(chart), 100);
							setTimeout(chart.reflow.bind(chart), 1000);
							ctrl.loaded = true;
						});
				}
			};
		}]);
;
"use strict";
angular.module("NGC.Registry")
	.service("ngcPriceHistoryService", [
		"webServiceFactory",
		function(webServiceFactory) {
			return webServiceFactory.create({
				getPriceHistory: function(coinID, grade, labelCoinID) {
					labelCoinID = labelCoinID ? labelCoinID + "/" : "";
					return this.get("/price-history/api/" + coinID + "/" + grade + "/" + labelCoinID)
						.then(function(result) {
							result.prices = _.map(result.prices, function(value) {
								return [new Date(value[0]).getTime(), value[1]];
							});
							return result;
						});
				},
				getPriceHistoryOverlay: function(coinID, grade, labelCoinID) {
					grade = grade.replace("+", "plus");
					labelCoinID = labelCoinID ? "?lc=" + labelCoinID : "";
					return this.get("/price-history/" + coinID + "/" + grade + "/" + labelCoinID);
				}
			});
		}
	]);;
"use strict";
angular.module("NGC.Registry")
	.controller("awardsController", [
		"$q", "awardsService", 
		function($q, awardsService) {
			var $ctrl = this;
			$ctrl.allAwards = [];
			$ctrl.awardSets = [];
			$ctrl.years = [];
			$ctrl.loaded = false;

			$ctrl.paperSizes = [{ Value: "Letter", Label: "8 1/2x11" }, { Value: "A4", Label: "A4" },];
			$ctrl.disableDownloadLink = true;

			var uri = new URI();
			var query = uri.search(true);

			awardsService.fetchAwards()
				.then(function(result) {
					$ctrl.allAwards = result.Awards || [];
					$ctrl.awardSetID = null;

					if(result.CoinRegAwardYear != null) {
						$ctrl.awardYearID = result.CoinRegAwardYear.AwardYearID;
						$ctrl.awardRequestYear = result.CoinRegAwardYear.AwardYear;
						$ctrl.awardRequestDeadline = new Date(result.CoinRegAwardYear.AwardRequestDeadline);
					}
					$ctrl.showPaperCopyRequest = ($ctrl.awardYearID && $ctrl.awardRequestYear);
					$ctrl.requestedPaperCopy = result.RequestedPaperCopy;

					$ctrl.years = _($ctrl.allAwards)
						.map("AwardYear")
						.uniq()
						.value();

					var queryAwardSet = _.find($ctrl.allAwards, { 'AwardSetID': parseInt(query.awardSet, 10) });
					if(queryAwardSet && queryAwardSet.AwardSetID) {
						$ctrl.year = queryAwardSet.AwardYear;
						$ctrl.changeYear();
						$ctrl.awardSetID = queryAwardSet.AwardSetID;
					} else if($ctrl.years && $ctrl.years.length > 0) {
						$ctrl.year = $ctrl.years[0].toString();
						$ctrl.changeYear();
					}

					$ctrl.loaded = true;
				});

			$ctrl.changeYear = function() {
				$ctrl.awardSets = _($ctrl.allAwards)
									.filter({ AwardYear: $ctrl.year })
									.sortBy(function(award) {
										return  {
											awardType: award.AwardType, 
											setName: award.SetName.toLowerCase()
										};
									})
									.value();

				$ctrl.awardSetID = null;
				$ctrl.disableDownloadLink = true;
			};

			$ctrl.updateDownloadLink = function() {
				$ctrl.disableDownloadLink = (!$ctrl.paperSize || !$ctrl.awardSetID);
				$ctrl.downloadUrl = "/profile/awards/" + $ctrl.awardSetID + "/download/" + $ctrl.paperSize + "/";
			};

			$ctrl.updatePaperCopyRequest = function() {
				if($ctrl.requestedPaperCopy) {
					return awardsService.insertAwardRequest($ctrl.awardYearID)
						.then(function(results) {
							if(results.HasAddress) {
								$ctrl.successMessage = "Changes Saved";
							} else {
								$ctrl.showAddAddressModal = true;
							}
						});
				} else {
					return awardsService.deleteAwardRequest($ctrl.awardYearID)
						.then(function() {
							$ctrl.successMessage = "Changes Saved";
						});
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("awardsService", [
		"webServiceFactory",
		function(webServiceFactory) {
			return webServiceFactory.create({
				fetchAwards: function() {
					return this.get("/profile/data/awards/");
				},
				insertAwardRequest: function(awardYearID) {
					return this.post("/profile/data/awards/" + awardYearID + "/");
				},
				deleteAwardRequest: function(awardYearID) {
					return this.post("/profile/data/awards/" + awardYearID + "/delete/");
				},
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcProfileImage", [
		// No dependencies
		function() {
			return {
				scope: true,
				controllerAs: "profileImageCtrl",
				controller: [
					"profileService",
					function(profileService) {
						var $ctrl = this;

						$ctrl.uploadProfileImage = function(imageFile, orientation) {
							return profileService.saveProfileImage(imageFile, orientation)
								.then(function(results) {
									if(results.Success) {
										$ctrl.profileImageUrl = results.FullUrl;
										$ctrl.hasImage = true;
									}
									
									return results;
								});
						};
						$ctrl.deleteProfileImage = function() {
							return profileService.deleteProfileImage()
								.then(function() {
									$ctrl.profileImageUrl = null;
									$ctrl.hasImage = false;
								});
						};
					}
				],
				link: function(scope, elem, attrs, ctrl) {
					ctrl.profileImageUrl = attrs.profileImageUrl;
					ctrl.hasImage = !!ctrl.profileImageUrl;
					scope.$broadcast("bgImage.refresh");
				}
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("profileService", [
		"webServiceFactory", "Upload",
		function(webServiceFactory, Upload) {
			return webServiceFactory.create({
				saveProfileImage: function(newProfilePhoto, orientation) {
					return Upload.upload({ method: "PUT", url: "/profile/data/profile-photo/", data: { profilePhoto: newProfilePhoto, orientation: orientation } })
						.then(function(results) {
							return results.data;
						});
				},
				deleteProfileImage: function() {
					return this.post("/profile/data/profile-photo/delete/");
				},
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("userCoinListingController", [
		"$attrs", "$scope", "$filter", "$timeout", "userCoinsService",
		function($attrs, $scope, $filter, $timeout, userCoinsService) {
			var $ctrl = this;
			var params = (new URI()).search(true);
			$ctrl.orderBy = params["orderBy"] || "";
			$ctrl.hasFetchedData = false;
			$ctrl.peopleID = $attrs.peopleId;
			$ctrl.isLoading = false;

			$ctrl.loadCoins = function(coinDeleted) {
				$ctrl.isLoading = true;
				userCoinsService.fetchUserCoins($ctrl.peopleID, $ctrl.page || 1, $ctrl.orderBy, $ctrl.filter)
					.then(function(results) {
						$ctrl.hasFetchedData = true;
						results.Coins.forEach(function(coin) {
							coin.coinDetailUrl = "/registry/coins/" + coin.SetCoinID + "/";
						});
						
						$ctrl.hasFilter  = !!$ctrl.filter;
						$ctrl.isOwner    = results.IsOwner;
						$ctrl.isAdmin    = results.IsAdmin;
						$ctrl.showPrice  = (results.IsAdmin || results.IsOwner);
						$ctrl.coins      = results.Coins;
						$ctrl.filterText = results.FilterText;

						$ctrl.isLoading = false;
						//timeout to let the bindings update before broadcasting
						$timeout(function() {
							$scope.$broadcast("coinListing.updateCoins", {coinCount: $ctrl.coins.length, keepUrl: coinDeleted});
						});
					});
			};

			$ctrl.loadCoins();

			$scope.$on("userCoinList.coinDeleted", function() {
				$ctrl.loadCoins(true);
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("userCoinsService", [
		"webServiceFactory", "urlService",
		function(webServiceFactory, urlService) {
			return webServiceFactory.create({
				fetchUserCountries: function(peopleID) {
					var url = "resources/services/registry/usersets/" + peopleID + "/countries/";
					return this.get(url);
				},
				fetchUserCategories: function(peopleID, countryID) {
					var url = "resources/services/registry/usersets/" + peopleID + "/categories/";
					if(countryID)
						url += "?countryID=" + countryID;
					return this.get(url);
				},
				fetchUserCoins: function(peopleID, page, orderBy, filter) {
					var url = "resources/services/registry/usercoins/" + peopleID + "/" + page + "/";
					url += "?orderBy=" + orderBy;
					if(filter)
						url += "&filter=" + encodeURIComponent(filter);
					return this.get(url);
				},
				fetchSetCountsForUserCoins: function(peopleID, page, orderBy, filter) {
					var url = "resources/services/registry/setcounts/" + peopleID + "/" + page + "/";
					url += "?orderBy=" + orderBy;
					if(filter)
						url += "&filter=" + encodeURIComponent(filter);
					return this.get(url);
				},
				fetchUserSets: function (peopleID, page, orderBy, filters) {
					var url = "resources/services/registry/usersets/" + peopleID + "/" + page + "/";
					var parameters = { orderBy: orderBy };
					if(filters)
						parameters = _.assign(parameters, filters);
					url = urlService.buildUrl(url, parameters);
					return this.get(url);
				},
				fetchUserSetTypes: function(peopleID, categoryID) {
					var url = "resources/services/registry/usersets/" + peopleID + "/settypes/";
					if(categoryID)
						url += "?categoryID=" + categoryID;
					return this.get(url);
				},
				fetchUserSetsForSetType: function(peopleID, setTypeID) {
					var url = "resources/services/registry/usersets/" + peopleID + "/settypes/" + setTypeID + "/";
					return this.get(url);
				}
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("userSetsController", [
		"$rootScope", "$scope", "$window", "urlService", "userCoinsService",
		function($rootScope, $scope, $window, urlService, userCoinsService) {
			var self = this;
			var uri = new URI();
			var params = uri.search(true);
			self.peopleID = uri.segment(-3);
			self.page = params["page"] || 1;
			self.baseUrl = urlService.baseUrl;
			self.filters = {
				countryID: 0,
				categoryID: 0,
				setTypeID: 0,
			};
			self.orderBy = "";
			self.isMySets = true;

			var sortForCountries = CCG.culture === "zh-CN" || CCG.culture === "zh-TW" ? "PinYin" : "SEOFriendlyName";
			userCoinsService.fetchUserCountries(self.peopleID)
				.then(function(results) {
					self.countries = _.sortBy(results, sortForCountries);
					if(self.countries.length === 1) 
						self.country = self.countries[0];
				});

			function filterSets() {
				self.filters = {
					countryID: self.country ? self.country.ID : 0,
					categoryID: self.category ? self.category.ID : 0,
					setTypeID: self.setType ? self.setType.ID : 0
				};

				self.refresh(self.page);
			}
			self.refresh = function(page) {
				$("html, body").animate({ scrollTop: 0 }, 500);
				self.page = page || 1;

				userCoinsService.fetchUserSets(self.peopleID, self.page, self.orderBy, self.filters)
					.then(function(results) {
						self.setsData = results;
						self.totalSetCount = self.setsData.TotalCount;
						self.awardCount = self.setsData.AwardCount;
						self.isOwner = self.setsData.IsOwner;
						var parameters = _.assign({}, self.filters, { orderBy: self.orderBy, page: self.page });
						urlService.updateUrl(self.baseUrl, parameters);
					});

			};
			self.countryChanged = function() {
				self.category = null;
				self.setType = null;
				self.page = 1;
				var countryID = self.country ? self.country.ID : 0;
				userCoinsService.fetchUserCategories(self.peopleID, countryID)
					.then(function(categories) {
						self.categories = categories;
						if(self.categories.length === 1) {
							self.category = self.categories[0];
							self.categoryChanged();
						} else filterSets();
					});
			};
			self.categoryChanged = function() {
				self.setType = null;
				self.page = 1;
				var categoryID = self.category ? self.category.ID : null;
				if(!categoryID) {
					self.setTypes = [];
					filterSets();
					return;
				}

				userCoinsService.fetchUserSetTypes(self.peopleID, categoryID)
					.then(function(setTypes) {
						self.setTypes = setTypes;
						if(self.setTypes.length === 1) {
							self.setType = self.setTypes[0];
							self.setTypeChanged();
						} else filterSets();
					});
			};
			self.setTypeChanged = function() {
				self.page = 1;
				filterSets();
			};
			self.sortChanged = function() {
				filterSets();
			};

			self.countryChanged();
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcAddCoinModal", [
		"$window",
		function($window) {
			return {
				scope: true,
				controllerAs: "coinMgr",
				templateUrl: "template-cache/add-coin/add-coin-modal.html",
				controller: [
					"setService", "coinService", "$rootScope", "$scope", "user",
					function(setService, coinService, $rootScope, $scope, user) {
						var $ctrl = this;
						$scope.user = user;
					
						this.openCoinLookup = function(e, options) {
							options = options || {};

							if($scope.user.IsLoggedIn) {
								$ctrl.grader        = options.grader;
								$ctrl.preferredSlot = options.slot;
								$ctrl.certNumber    = options.certNumber;
								changeViewMode("lookup");
							} else {
								$rootScope.$broadcast("addCoin.loginOrJoin");
							}
						};
						this.openAddCoinImages = function(e, options) {
							if(!options) {
								console.error("openAddCoinImages: No options provided.")
								return;
							}

							coinService
								.checkCoinDetails(options.peopleCoinID)
								.then(function(results) {
									$ctrl.coinDetailsResults = {
										CoinSummary: options.summary,
										PreferredSlot: options.slot,
										Results: results,
									};
									changeViewMode("images");
								});
						};

						this.openSelectSetType = function(e, peopleCoinID, clearPreferredSlot) {
							if(clearPreferredSlot)
								$ctrl.preferredSlot = null;

							if($ctrl.preferredSlot) {
								validateSlot(peopleCoinID);
							} else {
								setService
									.fetchAvailableSetTypes(peopleCoinID)
									.then(function(results) {
										$ctrl.availableSetTypes = results;
										changeViewMode("set-type");
									}, handleError);
							}
						};
						this.openSelectExistingSets = function(e, peopleCoinID, clearPreferredSlot) {
							if(clearPreferredSlot)
								$ctrl.preferredSlot = null;

							if($ctrl.preferredSlot) {
								validateSlot(peopleCoinID);
							} else {
								setService
									.fetchAvailableSets(peopleCoinID, 0)
									.then(function(results) {
										if(results.EnableAddToSet) {
											$ctrl.availableSets = results;
											changeViewMode("existing-sets");
										} else {
											$ctrl.openSelectSetType(e, peopleCoinID);
										}
									}, handleError);
							}
						};
						this.openSelectSet = function(e, peopleCoinID, setTypeID, fetchAllSets) {
							setService
								.fetchAvailableSets(peopleCoinID, setTypeID, fetchAllSets)
								.then(function(results) {
									$ctrl.availableSets = results;
									$ctrl.selectSlot = fetchAllSets;
									changeViewMode("set");
									$rootScope.$broadcast("tooltips.hide");
								}, handleError);
						};

						this.openEditSlot = function(e, slot) {
							$ctrl.preferredSlot = slot;
							setService
								.fetchAvailableCoins(slot)
								.then(function(results) {
									$ctrl.availableCoins = results;
									changeViewMode("slot");
								}, handleError);
						};
						this.openConfirmSlot = function(slotResults) {
							$ctrl.slotResults = slotResults;
							changeViewMode("confirm-slot");
						};
						this.selectSlot = function(e, slot) {
							// If there are coins that can fit in the slot, show edit slot
							// Otherwise show lookup coin, within the context of a slot.
							if(slot.hasCoins || slot.isFilled)
								$ctrl.openEditSlot(e, slot);
							else $ctrl.openCoinLookup(e, { slot: slot });
						};

						this.openRequestNewSetType = function(e, coinSummary) {
							$ctrl.coinSummary = coinSummary;
							changeViewMode("request-new-set-type");
						};
						this.openRequestNewSlot = function(e, coinSummary, setTypeID, peopleSetID) {
							$ctrl.coinSummary = coinSummary;
							$ctrl.setTypeID = setTypeID;
							$ctrl.peopleSetID = peopleSetID;
							changeViewMode("request-new-slot");
						};

						this.close = function() {
							$ctrl.grader        = null;
							$ctrl.preferredSlot = null;
							$ctrl.certNumber    = null;
							changeViewMode();
						};

						// == Helpers =================================================================
						// ============================================================================
						function changeViewMode(mode) {
							$ctrl.viewMode	= mode;
							$ctrl.visible = (mode != null);
							if(mode == "lookup" && $ctrl.certNumber)
								$scope.$apply();

							$rootScope.$broadcast("ccgModal.resetScroll");
						}
						function handleError() {
							// TODO
						}
						function validateSlot(peopleCoinID) {
							setService
								.validateSlot(peopleCoinID, $ctrl.preferredSlot)
								.then(function(results) {
									if(results.IsValidForSlot) {
										addToPreferredSlot(results).then($ctrl.close);
									} else {
										$ctrl.openConfirmSlot(results);
									}
								}, handleError);
						}
						function addToPreferredSlot(results) {
							return setService
								.addToSet(results.CoinSummary.PeopleCoinID, $ctrl.preferredSlot)
								.catch(handleError);
						}
					}
				],
				link: function(scope, element, attrs, ctrl) {
					scope.$on("addCoin.openLookup",         ctrl.openCoinLookup);
					scope.$on("addCoin.addCoinImages",		ctrl.openAddCoinImages);
					scope.$on("addCoin.selectExistingSets", ctrl.openSelectExistingSets);
					scope.$on("addCoin.selectSetType",		ctrl.openSelectSetType);
					scope.$on("addCoin.setTypeSelected", 	ctrl.openSelectSet);
					scope.$on("addCoin.requestNewSetType", 	ctrl.openRequestNewSetType);
					scope.$on("addCoin.requestNewSlot", 	ctrl.openRequestNewSlot);
					scope.$on("addCoin.slotSelected", 		ctrl.selectSlot);
					scope.$on("addCoin.close", 				ctrl.close);

					if($window.location.search) {
						var params       = (new URI()).search(true);
						var peopleCoin   = params["add"];
						var setType      = params["settype"];
						var existingSets = params["existingsets"];
						var certNumber   = params["addCert"];

						if((peopleCoin || certNumber) && scope.user.UserType === "Dealer")
							return;

						if(peopleCoin && peopleCoin == "0") {
							ctrl.openCoinLookup();
						} else if(peopleCoin && setType) {
							ctrl.openSelectSet(null, peopleCoin, setType);
						} else if(peopleCoin && existingSets) {
							ctrl.openSelectExistingSets(null, peopleCoin);
						} else if(peopleCoin) {
							ctrl.openSelectSetType(null, peopleCoin, false);
						} else if(certNumber) {
							ctrl.openCoinLookup(null, {
								grader: "NGC",
								certNumber: certNumber
							});
						}
					}
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcCoinListing", [
		"urlService",
		function(urlService) {
			return {
				scope: {
					coins:                 "=",
					isAdmin:               "=",
					isOwner:               "=",
					showPrice:             "=",
					ownerID:               "@ownerId",
					ownerName:             "@ownerName",
					peopleSetID:           "@peopleSetId",
					setTypeID:             "@setTypeId",
					awardSetID:            "@awardSetId",
					slotDefinitionBaseUrl: "@",
				},
				templateUrl: urlService.templateUrl("registry/partials/coin-listing/"),
				bindToController: true,
				controllerAs: "$ctrl",
				controller: [
					"$rootScope", "$filter", "$location", "coinService", "setService",
					function($rootScope, $filter, $location, coinService, setService) {
						var $ctrl = this;
						$ctrl.pageSize = 50;
						$ctrl.replaceGradeIcons = coinService.replaceGradeIcons;

						$ctrl.openEditSlot = function(coin) {
							var slot = {
								peopleSetID: $ctrl.peopleSetID,
								slotID     : coin.SlotID,
								isFilled   : coin.SlotID && coin.SlotFilled,
								hasCoins   : (coin.AvailableCoinCount > 0),
								isUSCategory : ($ctrl.isUsCategory == undefined || $ctrl.isUsCategory)
							};

							$rootScope.$broadcast("addCoin.slotSelected", slot);
						};

						$ctrl.openAddToSets = function(coin) {
							$rootScope.$broadcast("addCoin.selectExistingSets", coin.SetCoinID);
						};

						$ctrl.handleDescriptionClick = function(e, coin) {
							if(!$ctrl.awardSetID)
								return;

							e.preventDefault();
							e.stopPropagation();

							$ctrl.currentCoin = coin;
							coinService.getAwardCoinComments(coin.SetCoinID)
								.then(function(comments) {
									$ctrl.currentCoin.OwnerComments = comments;
									$ctrl.openCoinDetailsModal = true;
								});
						};

						$ctrl.loadPage = function(e, args) {
							var page = 1;
							if(args && args.keepUrl) {
								var urlPage = $location.search().page;
								if(urlPage !== undefined)
									page = urlPage;
								else 
									$location.search("page", 1);
							} else {
								//if it's not first page load, clear page queryString
								$location.search("page", null);
							}
							$ctrl.pagination = {
								PageCount: Math.ceil(args.coinCount / $ctrl.pageSize)
							};

							$ctrl.pagination.Page = page <= $ctrl.pagination.PageCount ? page : $ctrl.pagination.PageCount;

							goToPage($ctrl.pagination.Page);
						};

						$ctrl.changePage = function(page) {
							//don't change the url if we're going to page 1 and don't already have a queryString
							//this is an edge case for when a user changes the filter, switches pages, then clicks the browser back
							if(page === 1 && $location.search().page === undefined)
								return;

							$location.search("page", page);
						};

						function goToPage(page) {
							page = Math.min(page, $ctrl.pagination.PageCount);
							$ctrl.startIndex = $ctrl.pageSize * (page - 1);
						}

						//url is updated on changePage which triggers this, as well as using browser back/forward
						$rootScope.$on("$locationChangeSuccess", function() {
							var page = $location.search().page;
							if(page === undefined)
								page = 1;

							//handle browser back/forward
							if(page !== $ctrl.pagination.Page) {
								//if user navigates back and the page they are on is higher than current page count
								//change the page to the last page to prevent weirdness.
								if(page > $ctrl.pagination.PageCount) {
									page = $ctrl.pagination.PageCount;
									$location.search("page", page).replace(); //replace() prevents history from being affected
								} 

								//trigger the watch function in pager.js to update the paginator page
								$ctrl.pagination.Page = page;
							}

							goToPage(page);
						});
					}
				],
				link: function(scope, elem, attrs, ctrl) {
					ctrl.isUsCategory = (attrs.isUsCategory === "true");
					//set column visibilities
					if(ctrl.awardSetID) {
						ctrl.showListingCert = false;
						ctrl.showAddRemove   = false;
						ctrl.showAddToSet    = false;
						ctrl.showDelete      = false;
						ctrl.showEbay        = true;
					} else {
						var isSetPage        = !!ctrl.peopleSetID;
						Object.defineProperty(ctrl, "showListingCert", { get: function() { return this.isOwner || this.isAdmin; } });
						Object.defineProperty(ctrl, "showAddRemove",   { get: function() { return this.isOwner && isSetPage; } });
						Object.defineProperty(ctrl, "showAddToSet",    { get: function() { return this.isOwner && !isSetPage; } });
						Object.defineProperty(ctrl, "showDelete",      { get: function() { return this.isOwner && !isSetPage; } });
						Object.defineProperty(ctrl, "showEbay",        { get: function() { return isSetPage; } });
					}

					scope.$on("coinListing.updateCoins", ctrl.loadPage);
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("registryCountriesController", [
		"$scope", "$rootScope", "storageService", "registryCountriesData",
		function($scope, $rootScope, storageService, registryCountriesData) {
			var ctrl = this;
			angular.extend($scope, registryCountriesData);
			$scope.orderBy = storageService.get("registry-countries-orderby");
			$scope.nameForSort = CCG.culture === "zh-CN" || CCG.culture === "zh-TW" ? "PinYin" : "Name";

			ctrl.orderByAlpha = function () {
				$scope.orderBy = "alpha";
				storageService.set("registry-countries-orderby", $scope.orderBy);
				$scope.countries = _.sortBy($scope.countries, $scope.nameForSort);
				$rootScope.$broadcast("ccgViewSwitch.viewSwitched");
			};
			ctrl.orderByPopulation = function() {
				$scope.orderBy = "population";
				storageService.set("registry-countries-orderby", $scope.orderBy);
				$scope.countries = _.orderBy($scope.countries, ["Population", $scope.nameForSort], ["desc", "asc"]);
				$rootScope.$broadcast("ccgViewSwitch.viewSwitched");
			};

			switch ($scope.orderBy) {
				case "alpha":
					ctrl.orderByAlpha();
					break;
				case "population":
				default:
					ctrl.orderByPopulation();
					break;
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("registrySetListingController", [
		"$attrs", "userCoinsService", "setService", "urlService", "user", 
		function($attrs, userCoinsService, setService, urlService, user) {
			var ctrl = this;
			ctrl.peopleID = user.PeopleID;
			ctrl.setTypeID = $attrs.setTypeId;
			ctrl.baseUrl = urlService.baseUrl;
			var params = (new URI()).search(true);
			ctrl.page = parseInt(params["page"]) || 1;

			ctrl.createSetClicked = function() {
				userCoinsService.fetchUserSetsForSetType(ctrl.peopleID, ctrl.setTypeID)
					.then(function(results) {
						ctrl.userSetsData = results;
						ctrl.showCreateSet = true;
					});
			};

			ctrl.refresh = function(page, skipUpdateUrl) {
				ctrl.page = page || 1;
				setService.fetchSetsBySetType(ctrl.setTypeID, ctrl.page)
					.then(function(results) {
						ctrl.setsData = results;
						ctrl.awardCount = ctrl.setsData.AwardCount;
						if(!skipUpdateUrl)
							urlService.updateUrl(ctrl.baseUrl + "?page=" + ctrl.page);
					});
			};

			ctrl.refresh(ctrl.page, true);
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcSetDetail", [
		// No dependencies
		function() {
			return {
				scope: true,
				controllerAs: "set",
				controller: [
				    "setService",
					function(setService) {
						var $ctrl = this;
						$ctrl.setHistoryModalVisible = false;

						$ctrl.updateSetInfo = function(e) {
							setService.getSet($ctrl.peopleSetID)
								.then(function(setInfo) {
									$ctrl.name			= setInfo.Name;
									$ctrl.rank			= setInfo.RankDisplay;
									$ctrl.description	= setInfo.Description;
									$ctrl.setScore		= setInfo.Score;
									$ctrl.lastModified	= setInfo.LastModified;
									$ctrl.setImageUrl	= setInfo.FullImage;
									$ctrl.isObscured	= setInfo.IsObscured;
									$ctrl.totalPrice    = setInfo.TotalPrice;
								});
						};

						$ctrl.uploadSetImage = function(imageFile, orientation) {
							return setService.updateSetCoverImage($ctrl.peopleSetID, imageFile, orientation)
								.then(function(results) {
									if(results.Success) {
										$ctrl.setImageUrl = results.FullUrl;
										$ctrl.hasImage = true;
									}

									return results;
								});
						};
						$ctrl.deleteSetImage = function() {
							return setService.deleteSetCoverImage($ctrl.peopleSetID)
								.then(function() {
									$ctrl.setImageUrl = null;
									$ctrl.hasImage = false;
								});
						};
						$ctrl.openSetGallery = function() {
							var $images = $("[data-fancybox='coin-listing']");
							$.fancybox.open($images, {
								thumbs: {
									autoStart: true
								}
							});
						};

						var setHistoryPromise = null;
						$ctrl.openSetHistory = function() {
							if(setHistoryPromise == null) {
								setHistoryPromise = setService.fetchSetHistory($ctrl.peopleSetID);
							}

							setHistoryPromise
								.then(function(results) {
									$ctrl.setHistory = results;
									$ctrl.certIsVisible = _.some(results, "CertNumber");
									$ctrl.setHistoryModalVisible = true;
								});
						};

						$ctrl.openNominationModal = function() {
							$ctrl.modalData = {};
							setService.fetchNominationData($ctrl.peopleSetID)
								.then(function(modalData) {
									if(!modalData) {
										$ctrl.modalData.allowNominations = false;
									} else {
										$ctrl.modalData = modalData;
										$ctrl.modalData.allowNominations = true;
										$ctrl.modalData.nominateAnyway = $ctrl.modalData.Eligibility.IsComplete 
																&& $ctrl.modalData.Eligibility.IsNgcGraded 
																&& !$ctrl.modalData.Eligibility.RecentlyObscured;

										$ctrl.modalData.isNominatedFor = {};
										$ctrl.modalData.CurrentNominations.forEach(function(n) {
											$ctrl.modalData.isNominatedFor[n.AwardTypeID] = true;
										});
										$ctrl.modalData.checkForNomination = function(awardIDKey) {
											var awardID = $ctrl.modalData.Config.AwardIDs[awardIDKey];
											return $ctrl.modalData.isNominatedFor[awardID];
										};
									}

									$ctrl.showNominationModal = true;
								});
						};

						$ctrl.saveNomination = function(nominationForm) {
							setService.saveNomination($ctrl.peopleSetID, $ctrl.nomination)
								.then(function(result) {
									$ctrl.showNominationModal = false;
									nominationForm.$setPristine();
									nominationForm.$setUntouched();
								});
						};
					}
				],
				link: function(scope, elem, attrs, ctrl) {
					ctrl.peopleSetID = attrs.ngcSetDetail;
					ctrl.setImageUrl = attrs.setImage;
					ctrl.rank        = attrs.rank;
					ctrl.isObscured  = attrs.isObscured;
					ctrl.hasImage    = !!ctrl.setImageUrl;

					scope.$on("set.refresh", ctrl.updateSetInfo);
					scope.$broadcast("bgImage.refresh");
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("slotListingController", [
		"$attrs", "$rootScope", "$scope", "$filter", "$timeout", "setService", "storageService", "urlService",
		function($attrs, $rootScope, $scope, $filter, $timeout, setService, storageService, urlService) {
			var $ctrl = this;
			$ctrl.peopleSetID = $attrs.peopleSetId;
			$ctrl.awardSetID  = $attrs.awardSetId;
			$ctrl.FILTER_KEY  = "view-slots-filter";
			$ctrl.filter = storageService.get($ctrl.FILTER_KEY) || "";
			$ctrl.isLoading = false;

			$ctrl.filterSlots = function(filter) {
				storageService.set($ctrl.FILTER_KEY, filter);
				$ctrl.filter = filter;
				updateVisibility();
			};

			$ctrl.autoBuildSet = function() {
				$scope.form.$submitting = true;
				setService.autoBuildSet($ctrl.peopleSetID)
					.then(function(result) {
						$ctrl.autoBuildSetResult = result;
						$scope.form.$submitting = false;
				});
			};

			$ctrl.closeAutoBuildModal = function() {
				$ctrl.showAutoBuildModal = false;
				if($ctrl.autoBuildSetResult && $ctrl.autoBuildSetResult.Success)
					window.location = urlService.baseUrl;
			};

			if($ctrl.peopleSetID) {
				$ctrl.isLoading = true;
				setService.fetchSlotsForSet($ctrl.peopleSetID)
					.then(function(results) {
						results.Coins.forEach(function(coin) {
							coin.coinDetailUrl = "/registry/competitive-sets/" + $ctrl.peopleSetID + "/coins/" + coin.SetCoinID + "/";
						});
						
						$ctrl.isOwner = results.IsOwner;
						$ctrl.isAdmin = results.IsAdmin;
						$ctrl.showPrice = (results.IsAdmin || results.IsOwner) && results.IsUSCategory;
						$ctrl.coins = results.Coins;
						updateVisibility();
						$ctrl.isLoading = false;
					});
			}

			if($ctrl.awardSetID) {
				$ctrl.isLoading = true;
				setService.fetchSlotsForAwardSet($ctrl.awardSetID)
					.then(function(results) {
						$ctrl.coins = results.Coins;
						updateVisibility();
						$ctrl.isLoading = false;
					});
			}

			function updateVisibility(e, args) {
				//awardSet should not be filtered
				if($ctrl.peopleSetID){
					$ctrl.coins.forEach(function(coin) {
						switch($ctrl.filter) {
							case "filled": coin.isHidden = !coin.SlotFilled; break;
							case "empty" : coin.isHidden = !!coin.SlotFilled; break;
							default      : coin.isHidden = false;
						}
					});
					$ctrl.visibleCoins = $filter("filter")($ctrl.coins, function(coin) { return !coin.isHidden; });
				}

				//timeout to let the bindings update before broadcasting
				if($ctrl.visibleCoins) {
					$timeout(function() {
						$scope.$broadcast("coinListing.updateCoins", {coinCount: $ctrl.visibleCoins.length, keepUrl: args && args.keepUrl});
					});
				}
			}

			$ctrl.updateSlot = function(event, results) {
				var slotIndex = results && _.findIndex($ctrl.coins, function(coin) {
					return coin.SlotID === results.SlotID;
				});

				if(slotIndex >= 0) {
					setService.getCoinInSlot($ctrl.peopleSetID, results.SlotID)
						.then(function(coin) {
							var oldCoin = $ctrl.coins[slotIndex];
							coin.Index    = oldCoin.Index;
							if(coin.SetCoinID)
								coin.coinDetailUrl = "/registry/competitive-sets/" + $ctrl.peopleSetID + "/coins/" + coin.SetCoinID + "/";

							$ctrl.coins[slotIndex] = coin;
							$rootScope.$broadcast("coin.refresh-" + results.SlotID, coin);
							$rootScope.$broadcast("set.slotEdited", {keepUrl: true});
						});
				}
			};

			$scope.$on("set.refresh", $ctrl.updateSlot);
			$scope.$on("set.slotEdited", updateVisibility);
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("slotScoresListingController", [
		"$window", "$attrs", "$scope", "setService", "scoreGridFiltersService", "urlService", "scrollService",
		function($window, $attrs, $scope, setService, scoreGridFiltersService, urlService, scrollService) {
			var $ctrl = this;
			var gridKey = "set-scores";

			$ctrl.page = 1;
			$ctrl.isAdmin = $attrs.isAdmin === "true";
			$ctrl.isWorldSet = $attrs.isWorldSet === "true";
			$ctrl.setTypeID = parseInt($attrs.setTypeId);
			$ctrl.noValidScore = false;
            $ctrl.slotID = null;

			setService.fetchSlotsForSetType($ctrl.setTypeID)
				.then(function(slots) {
					$ctrl.slots = slots.slice(1); // the first slot has a -1 ID and it's for requesting a new slot

					// CCG-51533: add All Slots option at the end
					var allSlotsOption = {
						ID: 0,
						Name: $attrs.allSlotsLabel,
						SortOrder: slots[slots.length - 1].SortOrder + 10
					};
					$ctrl.slots.push(allSlotsOption);
				});
				
			if($window.location.hash) {
				$ctrl.slotID = $window.location.hash.replace("#", "");
				if($ctrl.slotID === "all")
					$ctrl.slotID = 0;
				$ctrl.slotID = parseInt($ctrl.slotID);
				if($ctrl.slotID || $ctrl.slotID === 0) {
					populateScores();
				}
			}

			// == Listener ==========================================================
			scoreGridFiltersService.onDataFilterChanged(applyDataFilter);

			$ctrl.selectSlot = function() {
				$ctrl.page = 1;
				populateScores();
				urlService.replaceUrl(urlService.baseUrl + "#" + ($ctrl.slotID === 0 ? "all" : $ctrl.slotID));
				scrollService.scrollToElement(".page-title-header");
			}

			$ctrl.changePage = function(page) {
				$ctrl.page = page || 1;
				populateScores();
			};

			function applyDataFilter() {
				populateScores(true);
			}

			function populateScores(reloadHeader) {
				if($ctrl.slotID === null || $ctrl.slotID < 0)
					return;

				$ctrl.isLoading = true;
				var filterValues = scoreGridFiltersService.getFilters(gridKey, $ctrl.isWorldSet);
				filterValues.page = $ctrl.page;

				// CCG-51533: if All Slots is selected, load all slots/coins/scores in this set
				if($ctrl.slotID === 0) {
					setService.fetchCoinScoresForAllSlots($ctrl.setTypeID, filterValues)
						.then(function(result) {
							populateScoresTable(result, reloadHeader);
						});
				} else {
					setService.fetchCoinScoresForSlot($ctrl.slotID, filterValues)
						.then(function(result) {
							populateScoresTable(result, reloadHeader);
						});
					}
			}
			
			function populateScoresTable(result, reloadHeader) {
				var rowIndex = 0;
				var preCoinID = 0;
				//to alternate the background color for each coin
				_.forEach(result.CoinScores, function(coinScore) {
					if(preCoinID != coinScore.CoinID) {
						preCoinID = coinScore.CoinID;
						rowIndex++;
					}
					coinScore.IsOddRow = rowIndex % 2 !== 0;
				});

				$ctrl.slot = result;
				$ctrl.isLoading = false;
				$ctrl.noValidCoins = $ctrl.slot.CoinScores.length == 0;

				if(reloadHeader)
					$scope.$emit("ngcCoinGridStickyHeader.reload.main");
			}

			// == Score Correction Modal ============================================
			$ctrl.scoreCorrection = {
				comments: "",
				isSubmitting: false,
				showError: false,
				showModal: false,
				slotCoins: [],
			};

			$ctrl.openScoreCorrectionModal = function() {
				$ctrl.scoreCorrection.comments = "";
				$ctrl.scoreCorrection.showError = false;
				$ctrl.scoreCorrection.showModal = true;
				$ctrl.scoreCorrection.slotCoins = [];
				$ctrl.addScoreCorrectionSlotCoin();
				$ctrl.scoreCorrection.slotCoins[0].selectedSlot = _.find($ctrl.slots, { ID: $ctrl.slotID === 0 ? null : $ctrl.slotID });
				if($ctrl.scoreCorrection.slotCoins[0].selectedSlot) {
					$ctrl.changeScoreCorrectionSlot(0);
				}

				$scope.scoreCorrectionForm.$setPristine();
			};

			$ctrl.addScoreCorrectionSlotCoin = function() {
				if($ctrl.scoreCorrection.slotCoins.length === 5) {
					return;
				}

				$ctrl.scoreCorrection.slotCoins.push({
					isLoading: false,
					selectedSlot: null,
					selectedCoin: null,
					coinList: null
				});

				$ctrl.scoreCorrection.showAddSlotCoin = ($ctrl.scoreCorrection.slotCoins.length < 5);
			};

			$ctrl.changeScoreCorrectionSlot = function(index) {
				var currentSlotCoin = $ctrl.scoreCorrection.slotCoins[index];
				currentSlotCoin.coinList = [];

				var slotID = _.get(currentSlotCoin.selectedSlot, "ID");
				if(!slotID)
					return;

				currentSlotCoin.isLoading = true;

				setService.fetchCoinsForSlot(slotID)
					.then(function(results) {
						currentSlotCoin.isLoading = false;
						currentSlotCoin.coinList = results;
					});
			};

			$ctrl.submitScoreCorrectionRequest = function() {
				var selectedSlotCoins = [];
				$ctrl.scoreCorrection.slotCoins.forEach(function(slotCoin) {
					if(slotCoin.selectedSlot == null && slotCoin.selectedCoin == null) {
						return;
					}

					selectedSlotCoins.push({
						SlotID         : _.get(slotCoin.selectedSlot, "ID"),
						SlotName       : _.get(slotCoin.selectedSlot, "Name"),
						CoinID         : _.get(slotCoin.selectedCoin, "CoinID"),
						CoinDescription: _.get(slotCoin.selectedCoin, "CoinDescription"),
					});
				});

				$ctrl.scoreCorrection.isSubmitting = true;
				setService.requestScoreCorrection($ctrl.setTypeID, selectedSlotCoins, $ctrl.scoreCorrection.comments)
					.then(function(result) {
						if(result.Success) {
							$ctrl.scoreCorrection.showModal = false;
						} else {
							$ctrl.scoreCorrection.showError = true;
						}
					})
					.catch(function() {
						$ctrl.scoreCorrection.showError = true;
					})
					.finally(function() {
						$ctrl.scoreCorrection.isSubmitting = false;
					});
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("totalRankingsController", [
		"$scope", "$element", "$timeout", "$location", "totalRankingsService", "urlService", "ngcScrollService", 
		function($scope, $element, $timeout, $location, totalRankingsService, urlService, ngcScrollService) {
			var $ctrl = this;
			$ctrl.criteria = (new URI()).search(true); // get the query string parsed into an object
			$ctrl.baseUrl = urlService.baseUrl;
			$ctrl.isLoading = false;

			$ctrl.criteria.page = $ctrl.criteria.page || 1;
			$ctrl.criteria.filter = $ctrl.criteria.filter || "All";
			$ctrl.criteria.sort = $ctrl.criteria.sort || "Rank";
			$ctrl.criteria.sortDirection = $ctrl.criteria.sortDirection || "ASC";
			$ctrl.sortIndicator = $ctrl.criteria.sortDirection === "ASC" ? "up" : "down";

			urlService.replaceUrl($ctrl.baseUrl, $ctrl.criteria);

			$scope.$on("$locationChangeSuccess", function() {
				loadData();
			});

			function loadData() {
				$ctrl.isLoading = true;

				totalRankingsService.fetchRegistryRankings($ctrl.criteria)
					.then(function(result) {
						$ctrl.registryRankings  = result;
						// Scroll to the user if peopleID is present in the query params and the user is on this page
						if($ctrl.criteria.peopleID)
							$timeout(function() {
								var scrollToRow = $element.find(".row-highlight");
								if(scrollToRow.length > 0)
									// Parameters: (element, duration, container, offset, overrideOffset)
									// offset is set to -200 so the row scrolled to is not at the very the top
									ngcScrollService.performScroll(scrollToRow, 250, null, -200, null);
							});

						$ctrl.isLoading = false;
					});
			};

			$ctrl.changeCriteria = function(name, value) {
				switch(name) {
					case "filter":
						$ctrl.criteria.filter = value || "All";
						$ctrl.criteria.page = 1;
						break;
					case "sort":
						//change the sort direction only if the same sort column is selected
						if($ctrl.criteria.sort === value) {
							$ctrl.criteria.sortDirection = $ctrl.criteria.sortDirection === "ASC" ? "DESC" : "ASC";
						} else {
							$ctrl.criteria.sort = value || "Rank";
							$ctrl.criteria.sortDirection = "ASC";
						}
						$ctrl.criteria.page = 1;
						$ctrl.sortIndicator = $ctrl.criteria.sortDirection === "ASC" ? "up" : "down";
						break;
					case "page":
						$ctrl.criteria.page = value || 1;
						break;
				}
				
				urlService.replaceUrl($ctrl.baseUrl, $ctrl.criteria);
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("totalRankingsService", [
		"webServiceFactory",
		function(webServiceFactory) {
			return webServiceFactory.create({
				fetchRegistryRankings: function(criteria) {
					return this.get("/resources/services/registry/competitive/registry-rankings/", criteria);
				}
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("ngcRegistryCategoriesService", [
		"webServiceFactory",
		function(webServiceFactory) {
			return webServiceFactory.create({
				fetchCategoriesForCountry: function(country) {
					return this.get("/registry/competitive/data/" + country + "/categories/");
				}
			});
		}
	]);
;
"use strict";
angular.module("CCG.Shared")
	.component("ngcRegistryCategories", {
		templateUrl: "/resources/scripts/registry/category-cards/registry-categories.html",
		bindings: {
			rootUrl: "@",
			expanderKey: "@",
			expanderDataUrlRoot: "@",
			countryName: "@",
			countrySeoName: "@",
			primaryNameColumn: "@",
			secondaryNameColumn: "@",
			imageColumn: "@",
			totalCountColumn: "@",
			totalCountLabel: "@",
			subcategoryNameColumn: "@",
			subcategoryCountColumn: "@",
			subcategoryCountLabel: "@",
			detailCollectionName: "@",
			detailNameLabel: "@",
			detailNameColumn: "@",
			detailCountLabel: "@",
			detailCountColumn: "@",
			expandAllLabel: "@",
			collapseAllLabel: "@",
			placeholderImageUrl: "@",
		},
		controller: [
			"urlService", "ngcRegistryCategoriesService",
			function(urlService, ngcRegistryCategoriesService) {
				var $ctrl = this;

				$ctrl.baseUrl = urlService.baseUrl;
				$ctrl.$onInit = function() {
					ngcRegistryCategoriesService.fetchCategoriesForCountry($ctrl.countrySeoName)
						.then(function(results) {
							results.forEach(function(item) {
								item.url = urlService.baseUrl + item.SEOFriendlyName + "/";
								if(item[$ctrl.imageColumn]) {
									item.imageUrl = item[$ctrl.imageColumn];
								}
							});

							$ctrl.gridItems = results;
						});
				};
				$ctrl.parseExpanderData = function(card, results) {
					results.forEach(function(subcategory) {
						var detailData = {
							columns: []
						};
						var itemCount = subcategory[$ctrl.detailCollectionName].length;
						if(itemCount > 5) {
							var perColumn = Math.ceil(itemCount / 2);
							detailData.columns.push(_.take(subcategory[$ctrl.detailCollectionName], perColumn));
							detailData.columns.push(_.takeRight(subcategory[$ctrl.detailCollectionName], itemCount - perColumn));
						} else {
							detailData.columns.push(subcategory[$ctrl.detailCollectionName]);
						}
						subcategory.columns = detailData.columns;
					});

					return results;
				};
			}
		],
	});
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcCoinImage", [
		"urlService",
		function(urlService) {
			return {
				scope: {
					peopleCoinID: "=peopleCoinId",
					isOwner: "=",
					isObverse: "=",
					imageHref: "=",
					thumbHref: "=",
					showAddNGCImage: "=showAddNgcImage",
					showDeleteImageAtTop: "=",
					slotID: "=slotId",
					hasCoin: "=",
					caption: "@",
					noGallery: "@",
				},
				templateUrl: urlService.root + "resources/scripts/registry/coin-image/coin-image-template.html",
				controllerAs: "image",
				controller: [
					"$scope", "$rootScope", "$element", "$filter", "coinService", "imageResizer", "notifier",
					function($scope, $rootScope, $element, $filter, coinService, imageResizer, notifier) {
						var $this = this;

						$this.scopeID = $scope.$id;
						$this.side = $scope.isObverse ? "obv" : "rev";
						$this.menuListener = "edit-image-" + $this.side + "-" + $scope.peopleCoinID + "-" + $this.scopeID;
						$this.hasImage = !!$scope.imageHref;

						this.handleClick = function(e) {
							if(!$scope.noGallery && (!$scope.isOwner || $this.openGallery)) {
								$this.openGallery = false;
								this.closeMenu();
								return;
							}

							if(!$this.ignoreParentClick) {
								//if we're opening the menu, and we don't have an image, go straight to pick an image
								//otherwise, reset the menu back to start
								$this.showEditImage = !$this.hasImage;
								$this.showConfirmDelete = false;

								//if we don't have an image, and we can't use ngc image, automatically open the file picker;
								if($this.showEditImage && !$scope.showAddNGCImage) {
									openNewImageSelect();
								} else {
									$rootScope.$broadcast("mxToggle.open-menus", "remove");
									$rootScope.$broadcast("mxToggle." + $this.menuListener, "add");
								}

							} else {
								$this.ignoreParentClick = false;
							}

							e.preventDefault();
							e.stopPropagation();
						};
						this.deleteImage = function() {
							$this.ignoreParentClick = true;

							$this.closeMenu();
							$this.imageElement.toggleClass("loading");
							coinService.deleteCoinImage($scope.peopleCoinID, $this.side)
								.then(function() {
									$this.refresh(null, null);
									$this.imageUploaded = false;
									$this.showConfirmDelete = false;
								})
								.finally(function() {
									$this.imageElement.toggleClass("loading");
								});
						};
						this.onImageSelected = function(files) {
							if(!files || files.length == 0) {
								return;
							}

							var selectedFile = files[0];
							if(!selectedFile.type.match(/image.*/)) {
								handleError($filter("localize")("Error.UploadError"));
								return;
							}

							$this.imageElement.toggleClass("loading");

							imageResizer.resize(selectedFile, this.finishUpload);
						};
						this.finishUpload = function(file, orientation) {
							if(!file) {
								handleError($filter("localize")("Error.UploadError"));
								return;
							}
							if(file.size > 10000000) {
								handleError($filter("localize")("Error.UploadTooLarge"));
								return;
							}

							$this.closeMenu();

							coinService.updateCoinImage($scope.peopleCoinID, $this.side, file, orientation)
								.success(function(results, status, headers, config) {
									if(results.Success) {
										$this.refresh(results.FullUrl, results.ThumbnailUrl);
									} else if(results.OverSizeLimit)
										handleError($filter("localize")("Error.OverSizeLimit"));
									else handleError($filter("localize")("Error.UploadError"));
								})
								.catch(handleError)
								.finally(function() {
									$this.imageElement.toggleClass("loading");

									// Disable file element before updating value,
									// so the change event doesn't fire again in IE
									$this.imageElement.find(":file")
										.prop("disabled", true)
										.val(null)
										.prop("disabled", false);
								});
						};
						this.refresh = function(fullUrl, thumbUrl) {
							$scope.imageHref = fullUrl;
							$scope.thumbHref = thumbUrl;
							$this.hasImage = !!$scope.imageHref;

							if($this.imageElement) {
								$this.imageElement.attr({
									"full-url": $this.hasImage ? $scope.imageHref : null
								});
							}

							$scope.$broadcast("bgImage.refresh");
						};
						this.imageHolderAdded = function() {
							$this.imageElement = $element.find("[ngc-bg-image]");
							$this.imageElement.attr({
								"full-url": $this.hasImage ? $scope.imageHref : null
							});
						};
						this.closeMenu = function() {
							$this.showEditImage = false;
							$this.showConfirmDelete = false;
							if($this.menuListener)
								$rootScope.$broadcast("mxToggle." + $this.menuListener, "remove");
						};
						this.replaceImage = function() {
							if(!$scope.showAddNGCImage) {
								openNewImageSelect();
							} else {
								$this.showEditImage = true;
								$this.ignoreParentClick = true;
							}
						};
						this.addNGCImage = function() {
							$this.imageElement.toggleClass("loading");
							$this.closeMenu();
							$this.ignoreParentClick = true;
							
							coinService
								.lookupAndSaveNGCImage($scope.peopleCoinID, $scope.isObverse, !$scope.isObverse)
								.then(function(results) {
									if(results.Success) {
										var fullUrl, thumbUrl;
										if($scope.isObverse) {
											fullUrl = results.ObverseFullUrl;
											thumbUrl = results.ObverseThumbUrl;
										} else {
											fullUrl = results.ReverseFullUrl;
											thumbUrl = results.ReverseThumbUrl;
										}

										$this.refresh(fullUrl, thumbUrl);
									} else if(results.NoNGCImage)
										handleError($filter("localize")("Error.NoNGCImageAvailable"));
									else if(results.OverSizeLimit)
										handleError($filter("localize")("Error.OverSizeLimit"));
									else handleError($filter("localize")("Error.SaveError"));
								})
								.catch(handleError)
								.finally(function() {
									$this.imageElement.toggleClass("loading");
								});
						};

						function handleError(message) {
							notifier.showError(message, 5000);
						}
						function openNewImageSelect() {
							$("#select-image-" + $scope.peopleCoinID + $this.side + $this.scopeID).click();
						}
						
						if($scope.slotID) {
							$scope.$on("coin.refresh-" + $scope.slotID, function(event, coin) {
								$scope.peopleCoinID = coin.PeopleCoinID;
								$scope.showAddNGCImage = coin.ShowAddNGCImage;

								if($scope.isObverse)
									$this.refresh(coin.ObverseImagePath, coin.ObverseThumbPath);
								else
									$this.refresh(coin.ReverseImagePath, coin.ReverseThumbPath);
							});
						}
					}
				]
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.component("scoreGrid", {
		templateUrl: [
			function() {
				return "/resources/scripts/registry/components/grid/score-grid.html";
			}
		],
		transclude: true,
		bindings: {
			scores: "<",
			isWorldSet: "@",
		},
		controller: [
			"$rootScope", "$attrs", "$element", "$timeout", "scoreGridFiltersService", "windowWatcher",
			function($rootScope, $attrs, $element, $timeout, scoreGridFiltersService, windowWatcher) {
				var $ctrl = this;
				var gridKey = "set-scores";
				$ctrl.$onInit = function() {
					windowWatcher.onResize(alignAllRows, 50);
					scoreGridFiltersService.onVisualFilterChanged(applyVisualFilter);
					$ctrl.isWorldSet = $ctrl.isWorldSet === "true";

					var filterValues = scoreGridFiltersService.getFilters(gridKey, $ctrl.isWorldSet);
					applyVisualFilter(filterValues);

					var mouseIsDown = false;
					$element
						.on("mousedown", ".scrollable td", function(e) { mouseIsDown = true; })
						.on("mouseup",   ".scrollable td", function(e) { mouseIsDown = false; })
						.on("mouseover", ".scrollable td", function(e) {
							var $targetCell = $(e.target);
							if(!mouseIsDown) {
								highlightGradeThrottled($targetCell);
							}
						});
				};

				$ctrl.$onChanges = function(changesObj) {
					$timeout(alignAllRows);
				};

				// == Filters =================================================================
				// ============================================================================
				var $styleElem = $element.find("style");
				function applyVisualFilter(filters) {
					var toHide 			= [];
					var fromGradeIndex 	= $element.find(".scrollable th[grade='" + filters.fromGrade + "']").index();
					var toGradeIndex 	= $element.find(".scrollable th[grade='" + filters.toGrade + "']").index() + 3;

					if(fromGradeIndex > 0)				toHide.push(":nth-child(-n + " + fromGradeIndex + ")");
					if(toGradeIndex >= 0) 				toHide.push(":nth-child(n + " + toGradeIndex + ")");
					if(!filters.base) 					toHide.push(".base");
					if(!filters.star) 					toHide.push(".star", ".plus-star");
					if(!filters.plus) 					toHide.push(".plus", ".plus-star");

					var selectors = toHide
						.map(function(selector) {
							return ".scrollable td" + selector + ", \n.scrollable th" + selector;
						})
						.join(", \n");

					$styleElem.text(selectors + " { display: none; }");
					$rootScope.$emit("ngcCoinGridStickyHeader.reload.main");
				}

				// == Row Alignment ===========================================================
				// ============================================================================
				function alignRows(fullReload) {
					if(fullReload) {
						$element.find(".pinned tr, .scrollable tr")
							.addClass("needs-alignment")
							.css({ height: "" });
					}

					var leftRows = $element.find(".pinned tr.needs-alignment").removeClass("needs-alignment").toArray();
					var rightRows = $element.find(".scrollable tr.needs-alignment").removeClass("needs-alignment").toArray();

					leftRows.forEach(function(leftRow, index) {
						var $leftRow     = $(leftRow);
						var $rightRow    = $(rightRows[index]);
						
						if($leftRow[0] && $rightRow[0]) {						
							var leftHeight   = $leftRow[0].getBoundingClientRect().height;
							var rightHeight  = $rightRow[0].getBoundingClientRect().height;
							var pinnedHeight = Math.max(leftHeight, rightHeight);

							if(leftHeight !== pinnedHeight)  { $leftRow.height(pinnedHeight); }
							if(rightHeight !== pinnedHeight) { $rightRow.height(pinnedHeight); }
						}
					});
				}
				function alignNewRows() { alignRows(false); }
				function alignAllRows() { alignRows(true); }

				// == Cell Highlighting =======================================================
				// ============================================================================
				var previousGrade = null;
				var selectedGrade = null;
				var highlightGradeThrottled = _.throttle(highlightGrade, 16);
				function highlightGrade($cell) {
					requestAnimationFrame(function() {
						$element.find(".focused").removeClass("focused");
						$cell.addClass("focused");
						selectedGrade = $cell.attr("grade");

						if(selectedGrade === previousGrade) {
							return;
						}

						$(".highlight[grade!=" + selectedGrade + "]").removeClass("highlight");
						$("[grade=" + selectedGrade + "]").addClass("highlight");

						previousGrade = selectedGrade;
					});
				}
			}
		]
	});
;
"use strict";
angular.module("NGC.Registry")
	.service("scoreGridFiltersService", [
		"$q", "coinGrades", "storageService", 
		function($q, coinGrades, storageService) {
			var currentFilters = null;
			var dataFilterCallbacks = [];
			var visualFilterCallbacks = [];

			return {
				STORAGE_KEY: "scoreGridFilters",
				onDataFilterChanged: function(callback) {
					dataFilterCallbacks.push(callback);
				},
				onVisualFilterChanged: function(callback) {
					visualFilterCallbacks.push(callback);
				},
				dataFilterChanged: function(filters) {
					dataFilterCallbacks.forEach(function(callback) {
						callback(filters);
					});
				},
				visualFilterChanged: function(filters) {
					visualFilterCallbacks.forEach(function(callback) {
						callback(filters);
					});
				},
				getFilters: function(gridKey, isWorldSet) {
					if(!currentFilters) {
						var completeGradeList = coinGrades[gridKey];
						currentFilters = storageService.get(this.STORAGE_KEY) || {
							base: true,
							star: true,
							plus: true,
							greenCAC: !isWorldSet,
							goldCAC: !isWorldSet,
							fromGrade: _.first(completeGradeList).code,
							toGrade: _.last(completeGradeList).code,
						};
					}

					return currentFilters;
				},
				saveFilters: function() {
					// Copy the object since we need to remove non-persistent properties
					var clone = angular.copy(currentFilters);
					storageService.set(this.STORAGE_KEY, clone);
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.component("scoreGridFilters", {
		templateUrl: "/Resources/scripts/registry/components/grid-filters/score-grid-filters.html",
		bindings: {
			gridKey              : "@",
			isWorldSet           : "@",
			filtersLabel         : "@",
			showFiltersLabel     : "@",
			fromLabel            : "@",
			toLabel              : "@",
			gradeLabel           : "@",
			baseGradesLabel      : "@",
			starGradesLabel      : "@",
			plusGradesLabel      : "@",
			greenCacLabel        : "@",
			goldCacLabel         : "@",
		},
		controller: [
			"$rootScope", "$scope", "coinGrades", "scoreGridFiltersService",
			function($rootScope, $scope, coinGrades, scoreGridFiltersService) {
				var $ctrl = this;
				var completeGradeList;

				$ctrl.$onInit = function() {
					$ctrl.fromGradeList = [];
					$ctrl.toGradeList   = [];					
					$ctrl.isWorldSet = $ctrl.isWorldSet === "true";

					completeGradeList      = coinGrades[$ctrl.gridKey];
					$ctrl.fromGradeList    = completeGradeList;
					$ctrl.toGradeList      = completeGradeList;
					$ctrl.filters          = scoreGridFiltersService.getFilters($ctrl.gridKey, $ctrl.isWorldSet);
					
					scoreGridFiltersService.onDataFilterChanged(updateDropdowns);
					scoreGridFiltersService.onVisualFilterChanged(updateDropdowns);
				};

				$ctrl.applyDataFilter = function() {
					applyFilters(true);
				};
				$ctrl.applyVisualFilter = function() {
					applyFilters(false);
					$scope.$emit("ngcCoinGridStickyHeader.reload.main");
				};

				function applyFilters(isDataFilter) {
					scoreGridFiltersService.saveFilters();

					if(isDataFilter) {
						scoreGridFiltersService.dataFilterChanged($ctrl.filters);
					} else {
						scoreGridFiltersService.visualFilterChanged($ctrl.filters);
					}
				}
				function updateDropdowns() {
					if(!$ctrl.filters) {
						return;
					}

					var fromGradeIndex  = _.findIndex(completeGradeList, { code: $ctrl.filters.fromGrade });
					var toGradeIndex    = _.findIndex(completeGradeList, { code: $ctrl.filters.toGrade });

					if(toGradeIndex === -1)
						toGradeIndex = completeGradeList.length;

					$ctrl.fromGradeList = _.take(completeGradeList, toGradeIndex + 1);
					$ctrl.toGradeList   = _.drop(completeGradeList, fromGradeIndex);
				}
			}
		]
	});
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcAddCoinImages", [
		"urlService", "coinService",
		function(urlService, coinService) {
			return {
				templateUrl	: "template-cache/add-coin/add-coin-images.html",
				replace		: true,
				scope		: {
					coinDetailsResults: "=ngcAddCoinImages"
				},
				controllerAs: "ctrl",
				controller: [
					"$scope", "$filter", "domService",
					function($scope, $filter, domService) {
						var ctrl = this;
						ctrl.undoFailed = false;

						if(ctrl.hasImages || !domService.supportsFileReader)
							ctrl.goToNextStep();

						ctrl.saveImages = function() {
							// the only time these will equal the NGC*Image is if the user doesn't change the path
							// If there is an NGC image, save it to the coin
							var saveNGCObverse = ctrl.hasNGCObverse && ctrl.obverseImagePath == $scope.CoinSummary.NGCObverseImage;
							var saveNGCReverse = ctrl.hasNGCReverse && ctrl.reverseImagePath == $scope.CoinSummary.NGCReverseImage;
							if(saveNGCReverse || saveNGCObverse) {
								ctrl.isConfirmingImages = true;
								coinService.lookupAndSaveNGCImage(ctrl.peopleCoinID, saveNGCObverse, saveNGCReverse)
									.then(function() {
										ctrl.isConfirmingImages = false;
										ctrl.goToNextStep();
									});
							} else {
								ctrl.goToNextStep();
							}
						};
						ctrl.undoAddCoin = function() {
							coinService.undoAddCoinToCollection(ctrl.peopleCoinID)
								.then(function(success) {
									if(success)
										ctrl.goToCoinLookup();
									else
										ctrl.undoFailed = true;
							});
						};
						ctrl.goToNextStep = function() {
							if(!ctrl.isVerified) 
								return;

							if(ctrl.hasExistingSets) {
								$scope.$emit("addCoin.selectExistingSets", ctrl.peopleCoinID);
							} else {
								$scope.$emit("addCoin.selectSetType", ctrl.peopleCoinID);
							}
						};
						ctrl.goToAddToSetType = function() {
							$scope.$emit("addCoin.selectSetType", ctrl.peopleCoinID);
						};
						ctrl.goToCoinLookup = function() {
							$scope.$emit("addCoin.openLookup", { grader: $scope.CoinSummary.Grader });
						};
						function handleError(message) {
							if(!message)
								return;

							ctrl.errorMessage = message;
						}
					}
				],
				link: function(scope, element, attrs, ctrl) {
					scope.isDetails                = true;
					scope.CoinSummary              = scope.coinDetailsResults.CoinSummary;
					scope.CoinSummary.DisplayGrade = coinService.replaceGradeIcons(scope.CoinSummary.DisplayGrade);
					ctrl.hasImages                 = scope.coinDetailsResults.Results.HasImages;
					ctrl.hasExistingSets           = scope.coinDetailsResults.Results.SetsExistForCoin;
					ctrl.isNewCoin                 = scope.coinDetailsResults.Results.IsNewCoin;
					ctrl.peopleCoinID              = scope.CoinSummary.PeopleCoinID;
					ctrl.ngcImageServiceDown       = scope.CoinSummary.NGCImageServiceDown;
					ctrl.preferredSlot             = scope.coinDetailsResults.PreferredSlot;

					ctrl.obverseImagePath          = scope.CoinSummary.ObverseImage;
					ctrl.obverseThumbPath          = scope.CoinSummary.ObverseThumb;
					ctrl.reverseImagePath          = scope.CoinSummary.ReverseImage;
					ctrl.reverseThumbPath          = scope.CoinSummary.ReverseThumb;
					ctrl.hasNGCObverse             = scope.CoinSummary.HasNGCObverse;
					ctrl.hasNGCReverse             = scope.CoinSummary.HasNGCReverse;

					ctrl.isVerified                = scope.CoinSummary.bVerified;
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcAddCoinLookup", [
		"domService", "urlService",
		function(domService, urlService) {
			return {
				templateUrl	: "template-cache/add-coin/add-coin-lookup.html",
				replace		: true,
				scope		: {
					preferredSlot: "=ngcAddCoinLookup",
					certNumber   : "=certNumber",
					grader       : "=grader"
				},
				controllerAs: "ctrl",
				controller: [
					"$scope", "$rootScope", "coinService", "setService",
					function($scope, $rootScope, coinService, setService) {
						var ctrl = this;
						ctrl.grader         = $scope.grader;
						ctrl.certNumber     = $scope.certNumber;
						ctrl.entryDisabled  = false;
						ctrl.isUSCategory   = ($scope.preferredSlot == undefined || $scope.preferredSlot == false || $scope.preferredSlot.isUSCategory);
						if(!ctrl.isUSCategory)
							ctrl.grader = "NGC"; // prefill NGC if world slot is in context
						ctrl.showWorldError = (ctrl.grader === "PCGS" && !ctrl.isUSCategory);

						this.replaceGradeIcons = coinService.replaceGradeIcons;
						this.lookup = function(e) {
							if(!inputIsValid())
								return;

							$scope.form.$submitting = true;
							if(ctrl.grader === "NGC")
								lookupNGC();
							else lookupPCGS();
						};

						this.confirmCoin = function() {
							ctrl.isConfirming	= true;
							if(ctrl.grader === "NGC")
								confirmNGCCoin();
							else confirmPCGSCoin();
						};
						this.goToAddCoinImages = function() {
							$scope.$emit("addCoin.addCoinImages", {
								peopleCoinID: $scope.CoinSummary.PeopleCoinID,
								summary: $scope.CoinSummary,
								slot:    $scope.preferredSlot
							});
						};
						this.resetForm = function(grader, clearCertNum) {
							if(clearCertNum)
								ctrl.certNumber = "";

							ctrl.grader         = grader;
							ctrl.entryDisabled  = false;
							ctrl.isConfirming   = false;
							ctrl.results        = undefined;
							ctrl.showWorldError = (ctrl.grader === "PCGS" && !ctrl.isUSCategory);
							ctrl.requiredFieldMissing = false;
							ctrl.certIsInvalid        = false;

							$scope.form.$setPristine();
				            $scope.form.$setUntouched();
						};

						function inputIsValid() {
							ctrl.requiredFieldMissing = false;
							ctrl.certIsInvalid = false;

							if(!ctrl.certNumber || !ctrl.grader) {
								ctrl.requiredFieldMissing = true;
								return false;
							}

							//validate length
							var minCertLength = 7;
							var maxCertLength = ctrl.grader === "PCGS"? 9 : 17;
							if(ctrl.certNumber.length < minCertLength || ctrl.certNumber.length > maxCertLength) {
								ctrl.certIsInvalid = true;
								return false;
							}
							//validate format
							var certRegex = new RegExp(ctrl.grader === "PCGS" ? "[\\A-Za-z0-9]+" : "[\\d\\-\\(\\)]+");
							if(!certRegex.test(ctrl.certNumber))
							{
								ctrl.certIsInvalid = true;
								return false;
							}
							return true;
						}
						function lookupNGC() {
							coinService
								.lookupNGC(ctrl.certNumber, $scope.preferredSlot)
								.then(function(results) {
									ctrl.certNumber	= results.CertNumber;

									if(results.CoinSummary) {
										$scope.CoinSummary = results.CoinSummary;
										$scope.CoinSummary.LookupResult = results.Message;
										$scope.CoinSummary.IsNewCoin = results.IsAddedToCollection;
									}

									if(results.IsInMyCollection || results.IsAddedToCollection) {
										ctrl.goToAddCoinImages();
									} else {
										ctrl.results = results;
										ctrl.entryDisabled = true;

										if(results.RequiresConfirmation)
											ctrl.nextStep = "confirm-coin";
										else if(!results.IsProIVDown && !$scope.preferredSlot)
											ctrl.nextStep = "add-more-coin";
									}
								})
								.catch(handleError)
								.finally(function() {
									$scope.form.$submitting = false;
								});
						}
						function lookupPCGS() {
							coinService
								.lookupPCGS(ctrl.certNumber, $scope.preferredSlot)
								.then(function(results) {
									ctrl.certNumber	= results.CertNumber;

									if(results.CoinSummary) {
										$scope.CoinSummary = results.CoinSummary;
										$scope.CoinSummary.LookupResult = results.Message;
										$scope.CoinSummary.IsNewCoin = results.IsAddedToCollection;
									}

									if(results.IsInMyCollection || results.IsAddedToCollection) {
										ctrl.goToAddCoinImages();
									} else {
										ctrl.results = results;
										ctrl.entryDisabled = true;

										if(results.IsAddedRecently) {
											ctrl.nextStep = "add-more-coin";
										} else if(results.RequiresConfirmation) {
											ctrl.results.ShowWarning = true;
											ctrl.nextStep = "confirm-coin";
										}
									}
								})
								.catch(handleError)
								.finally(function() {
									$scope.form.$submitting = false;
								});
						}
						function confirmNGCCoin() {
							coinService
								.confirmCertNGC(ctrl.certNumber, $scope.preferredSlot)
								.then(function(results) {
									if(results.IsInMyCollection)
										ctrl.nextStep = "continue";
									else ctrl.nextStep = "add-more-coin";

									$.extend(ctrl.results, results);
									$scope.CoinSummary = results.CoinSummary;
									ctrl.entryDisabled = true;
								})
								.catch(handleError)
								.finally(function() {
									ctrl.isConfirming = false;
								});
						}
						function confirmPCGSCoin() {
							ctrl.isConfirming  = true;
							ctrl.entryDisabled = true;

							coinService
								.confirmCertPCGS(ctrl.certNumber, $scope.preferredSlot)
								.then(function(results) {
									if(results.IsAddedRecently) {
										ctrl.nextStep = "add-more-coin";
										ctrl.entryDisabled = false;
									} else if (results.IsInMyCollection) {
										ctrl.nextStep = "continue";
									} else if(!$scope.preferredSlot) {
										ctrl.nextStep = "add-more-coin";
									} else {
										ctrl.nextStep = "";
									}

									$.extend(ctrl.results, results);
									$scope.CoinSummary = results.CoinSummary;
								})
								.catch(handleError)
								.finally(function() {
									ctrl.isConfirming = false;
								});
						}

						function handleError() {
							// TODO
							ctrl.entryDisabled = false;
						}
						function hasPreferredSlot() {
							return ($scope.preferredSlot != null);
						}
					}
				],
				link: function(scope, element, attrs, controller) {
					scope.baseUrl = urlService.root;
				}
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("coinDetailController", [
		"$rootScope", "$window", "$filter", "coinService", "coinGrades", "registryAdminService", "urlService",
		function($rootScope, $window, $filter, coinService, coinGrades, registryAdminService, urlService) {
			var $ctrl = this;

			$ctrl.editingComments = false;
			$ctrl.ownerComments = "";
			var pcgsGrades      = coinGrades["pcgs"].slice(0);
			$ctrl.pcgsGradeList = _.reverse(pcgsGrades);


			$ctrl.editComments = function(form, peopleCoinID) {
				coinService.checkCoinDetails(peopleCoinID, true)
					.then(function(results) {
						if(results.IsPending) 
							$window.location.href = "/profile/" + results.OwnerID + "/coins/pending/";
						else {
							resetForm(form);
							$ctrl.commentModalVisible = true;
							$ctrl.modalComments = $ctrl.ownerComments.slice();
						}
					});				
			};

			$ctrl.editCoinID = function(form, peopleCoinID) {
				coinService.checkCoinDetails(peopleCoinID, true)
					.then(function(results) {
						if(results.IsPending) 
							$window.location.href = "/profile/" + results.OwnerID + "/coins/pending/";
						else {
							resetForm(form);
							$ctrl.coinIDModalVisible = true;
							$ctrl.newCoinID = null;
							$ctrl.isCoinIDValid = true;
							$ctrl.coinDescription = "";
						}
					});			
			};

			$ctrl.editCoinGrade = function(form, peopleCoinID) {
				coinService.checkCoinDetails(peopleCoinID, true)
					.then(function(results) {
						if(results.IsPending) 
							$window.location.href = "/profile/" + results.OwnerID + "/coins/pending/";
						else {
							resetForm(form);
							$ctrl.coinGradeModalVisible = true;
						}
					});		
			};

			$ctrl.updateCoinDescription = function() {
				if(!$ctrl.newCoinID) {
					$ctrl.coinDescription = "";
					$ctrl.isCoinIDValid = true;
					return;
				}

				registryAdminService.getCoinDescription($ctrl.newCoinID)
					.then(function(result) {
						$ctrl.coinDescription = result.CoinDescription;
						$ctrl.isCoinIDValid   = result.IsCoinIDValid && result.IsUSCoin;
					})
					.catch(function() {
						$ctrl.isCoinIDValid   = false;
					});
			};

			$ctrl.saveCoinID = function(form, peopleCoinID) {
				if(!$ctrl.newCoinID || !$ctrl.isCoinIDValid)
					return;

				if(!$ctrl.showConfirmCoinID) {
					$ctrl.showConfirmCoinID = true;
				} else {
					form.$submitting = true;
					registryAdminService.updateCoinIDForPeopleCoin(peopleCoinID, $ctrl.newCoinID)
						.then(function(result) {
							if(result.Success) {
								window.location = urlService.baseUrl;
							} else {
								form.$submitting = false;
								$ctrl.errorMessage = $filter("localize")("Error.SaveError");
							}
						});
				}
			}

			$ctrl.cancelSaveCoinID = function(form) {
				$ctrl.newCoinID = null;
				$ctrl.showConfirmCoinID = false;
				$ctrl.coinIDModalVisible = false;
				resetForm(form);
			}

			$ctrl.saveCoinGrade = function(form, peopleCoinID) {
				if(!$ctrl.newCoinGrade)
					return;

				if(!$ctrl.showConfirmCoinGrade) {
					$ctrl.showConfirmCoinGrade = true;
				} else {
					form.$submitting = true;
					registryAdminService.updateCoinGradeForPeopleCoin(peopleCoinID, $ctrl.newCoinGrade)
						.then(function(result) {
							if(result.Success) {
								window.location = urlService.baseUrl;
							} else {
								form.$submitting = false;
								$ctrl.errorMessage = $filter("localize")("Error.SaveError");
							}
						});
				}
			}

			$ctrl.cancelSaveCoinGrade = function(form) {
				$ctrl.newCoinGrade = null;
				$ctrl.showConfirmCoinGrade = false;
				$ctrl.coinGradeModalVisible = false;
				resetForm(form);
			}

			$ctrl.saveComments = function(peopleCoinID) {
				coinService.updateCoin(peopleCoinID, $ctrl.modalComments)
					.then(function(result) {
						if(result.Success) {
							$ctrl.ownerComments = result.OwnerComments;
							$ctrl.commentModalVisible = false;
						} else {
							$ctrl.errorMessage = $filter("localize")("Error.SaveError");
						}
					});
			};

			$ctrl.openAddCoinModal = function(peopleCoinID) {
				coinService.checkCoinDetails(peopleCoinID, true)
					.then(function(results) {
						if(results.IsPending) 
							$window.location.href = "/profile/" + results.OwnerID + "/coins/pending/";
						else 				
							$rootScope.$broadcast("addCoin.selectExistingSets", peopleCoinID, true);
					});		
			};

			$ctrl.openDeleteCoinModal = function(setCoinID, peopleID, certNumber, points, displayGrade) {
				if(!setCoinID)
					return;

				coinService.checkCoinDetails(setCoinID, true)
					.then(function(results) {
						if(results.IsPending)
							$window.location.href = "/profile/" + results.OwnerID + "/coins/pending/";
						else {
							$ctrl.coinToDelete = {
								SetCoinID   : setCoinID,
								CertNumber  : certNumber,
								Points      : points,
								DisplayGrade: displayGrade,
							};
							$ctrl.coinToDelete.RedirectUrl = "/profile/" + peopleID + "/coins/";
							$ctrl.showDeleteCoin = true;
						}
					});
			};

			$ctrl.openWrongCoinOrGradeModal = function(peopleCoinID) {
				coinService.checkCoinDetails(peopleCoinID, true)
					.then(function(results) {
						if(results.IsPending)
							$window.location.href = "/profile/" + results.OwnerID + "/coins/pending/";
						else
							$ctrl.wrongCoinOrGradeMode = "MainMenu";
					});
			};

			$ctrl.lookupCAC = function(peopleCoinID, removeCAC) {
				$ctrl.submitting = true;
				coinService.lookupAndUpdateCACLabel(peopleCoinID)
					.then(function(result) {
						if(result.Success) {
							if(!removeCAC) {
								if(!result.IsGreenCAC && !result.IsGoldCAC)
									$ctrl.wrongCoinOrGradeMode = "CACFailure";
								else $ctrl.wrongCoinOrGradeMode = "CACSuccess";
							} else if(removeCAC) {
								if(!result.IsGreenCAC && !result.IsGoldCAC)
									$ctrl.wrongCoinOrGradeMode = "CACRemovalSuccess";
								else $ctrl.wrongCoinOrGradeMode = "CACRemovalFailure";
							}
						}
						else $ctrl.wrongCoinOrGradeMode = "Error";

						$ctrl.submitting = false;
					});
			};

			$ctrl.relookupCACByAdmin = function(peopleCoinID) {
				coinService.checkCoinDetails(peopleCoinID, true)
					.then(function(results) {
						if(results.IsPending)
							$window.location.href = "/profile/" + results.OwnerID + "/coins/pending/";
						else {
							$ctrl.relookupError = null;
							coinService.lookupAndUpdateCACLabel(peopleCoinID)
								.then(function(result) {
									if(result.Success)
										window.location = urlService.baseUrl;
									else
										$ctrl.relookupError = result.ErrorMessage;
								});
						}
					});				
			};

			$ctrl.resetCoinToPending = function(peopleCoinID) {
				$ctrl.submitting = true;
				coinService.resetToPending(peopleCoinID, $ctrl.resetToPendingComments)
					.then(function(result) {
						if(result.Success)
							$ctrl.wrongCoinOrGradeMode = "ResetToPendingSuccess";
						else $ctrl.wrongCoinOrGradeMode = "Error";

						$ctrl.submitting = false;
					});
			};

			$ctrl.deleteCoin = function(peopleCoinID, peopleID) {
				$ctrl.submitting = true;
				coinService.deleteCoin(peopleCoinID)
					.then(function(result) {
						if(result.Success) {
							$ctrl.wrongCoinOrGradeMode = null;
							$window.location.href = "/profile/" + peopleID + "/coins/";
						} else {
							$this.submitting = false;
							$ctrl.wrongCoinOrGradeMode = "Error";
						}
					});
			};

			$ctrl.reloadPage = function() {
				$ctrl.wrongCoinOrGradeMode = null;
				$window.location = urlService.baseUrl;
			};

			function resetForm(form) {
				form.$setPristine();
				form.$setUntouched();
				$ctrl.errorMessage = null;
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("coinService", [
		"webServiceFactory", "$rootScope", "Upload",
		function(webServiceFactory, $rootScope, Upload) {
			return webServiceFactory.create({
				lookupNGC: function(certNumber, preferredSlot) {
					return this.post("registry/manage/coins/lookup/ngc/", {
						certNumber: certNumber,
						preferredSlot: preferredSlot
					});
				},
				lookupPCGS: function(certNumber, preferredSlot) {
					return this.post("registry/manage/coins/lookup/pcgs/", {
						certNumber: certNumber,
						preferredSlot: preferredSlot
					});
				},
				lookupAndUpdateCACLabel: function(peopleCoinID) {
					return this.post("registry/manage/coins/lookup/cac/", {
						peopleCoinID: peopleCoinID
					});
				},
				confirmCertNGC: function(certNumber, preferredSlot) {
					return this.post("registry/manage/coins/confirm-lookup/ngc/", {
						certNumber: certNumber,
						preferredSlot: preferredSlot
					});
				},
				confirmCertPCGS: function(certNumber, preferredSlot) {
					return this.post("registry/manage/coins/confirm-lookup/pcgs/", {
						certNumber: certNumber,
						preferredSlot: preferredSlot
					});
				},
				checkCoinDetails: function(peopleCoinID, skipSetInfoCheck) {
					return this.post("registry/manage/coins/" + peopleCoinID + "/check-coin-details/", {
						skipSetInfoCheck: skipSetInfoCheck
					});
				},
				getAwardCoinComments: function(awardCoinID) {
					return this.get("registry/coins/awards/" + awardCoinID + "/comments/");
				},
				deleteCoin: function(peopleCoinID) {
					return this.post("registry/manage/coins/" + peopleCoinID + "/delete/");
				},
				undoAddCoinToCollection: function(peopleCoinID) {
					return this.post("registry/manage/coins/" + peopleCoinID + "/undo/");
				},
				updateCoin: function(peopleCoinID, description) {
					return this.post("registry/manage/coins/" + peopleCoinID + "/", {
						description: description
					}).then(function(results) {
						if(results.Success) {
							var coinData = {
								peopleCoinID	: peopleCoinID,
								hasDescription	: (description.length > 0)
							};

							$rootScope.$broadcast("set.refresh");
							$rootScope.$broadcast("slot.refresh", coinData);
							$rootScope.$broadcast("coin.refresh", coinData);
						}

						return results;
					});
				},
				lookupAndSaveNGCImage: function(peopleCoinID, useObverseImage, useReverseImage) {
					return this.post("registry/manage/coins/" + peopleCoinID + "/ngc-images/", {
						peopleCoinID: peopleCoinID,
						useNGCObverseImage: useObverseImage,
						useNGCReverseImage: useReverseImage
					});
				},
				manageImagePath: function(coinID, side, fallback) {
					var url = this.cleanUrl("registry/manage/coins/" + coinID + "/" + side + "/");
					if(fallback)
						url += "fallback/";

					return url;
				},
				deleteCoinImage: function(peopleCoinID, side) {
					return this.post(this.manageImagePath(peopleCoinID, side) + "delete/");
				},
				updateCoinImage: function(peopleCoinID, side, file, orientation) {
					return Upload.upload({
						url	: this.manageImagePath(peopleCoinID, side),
						data: {
							image: file,
							orientation: orientation
						}
					});
				},
				processTransfer: function(transferForm) {
					return this.post("registry/manage/coins/process-transfer/", angular.toJson(transferForm));
				},
				resetToPending: function(peopleCoinID, ownerComments) {
					return this.post("registry/manage/coins/reset-to-pending/", {
						peopleCoinID: peopleCoinID,
						ownerComments: ownerComments
					});
				},
				replaceGradeIcons: function (grade, maxLength) {
					if(!grade)
						return "";

					if(maxLength && grade.length > maxLength)
						grade = grade.substring(0, maxLength - 1);

					return grade
						.replace("+", "<i class='plus-grade'></i>")
						.replace("*", "<i class='star-grade'></i>");
				},
				fetchCountries: function(gradingCompany) {
					return this.get("resources/services/registry/countries/" + gradingCompany);
				},
				fetchCategoriesForCountry: function(countryID, isPCGS, excludeCirculatedOnly) {
					return this.post("resources/services/registry/countries/" + countryID + "/categories/", {
						countryID: countryID,
						isPCGS:    isPCGS,
						excludeCirculatedOnly: excludeCirculatedOnly
					});
				},
				fetchSubcategoriesForCategory: function(countryID, categoryID) {
					return this.get("resources/services/registry/countries/" + countryID + "/categories/" + categoryID + "/subcategories/");
				},
				fetchSetTypesForSubcategory: function(countryID, categoryID, subcategoryID, peopleCoinID) {
					var url = "resources/services/registry/countries/" + countryID + "/categories/" + categoryID + "/subcategories/" + subcategoryID + "/settypes/";
					if(peopleCoinID != undefined)
						url += "?peopleCoinID=" + peopleCoinID;
					return this.get(url);
				},
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcDeleteCoin", [
		"$window", "urlService",
		function($window, urlService) {
			return {
				scope: {
					showModal: "=ngcDeleteCoin",
					coin: "="
				},
				templateUrl: urlService.templateUrl("registry/manage/coins/templates/delete-coin/"),
				bindToController: true,
				controllerAs: "ctrl",
				controller: [
					"$rootScope", "$window", "coinService",
					function($rootScope, $window, coinService) {
						var $this = this;
						this.deleteCoin = function(e) {
							$this.submitting = true;
							coinService.deleteCoin($this.coin.SetCoinID)
								.then(function(result) {
									if(result.Success) {
										if($this.coin.RedirectUrl) {
											$window.location.href = $this.coin.RedirectUrl;
										} else {
											$rootScope.$broadcast("userCoinList.coinDeleted");
											$this.showModal = false;
										}
									} else {
										$this.submitting = false;
										$this.deleteFailed = true;
									}
								});
						};
					}
				]
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("transferCoinController", [
		"$scope", "coinService",
		function($scope, coinService) {
			var $this = this;

			this.modal = {};
			this.accept = function() {
				$this.transferAction = "Accept";
			};
			this.reject = function() {
				$this.transferAction = "Reject";
			};
			this.processTransfer = function() {
				$scope.form.$submitting = true;

				var promise = coinService.processTransfer({
						TransferID		: $this.transferID,
						TransferAction	: $this.transferAction,
						RejectReason	: $this.reason
					})
					.then(function(result) {
						$this.modal.success				= result.Success;
						$this.modal.transferProcessed	= result.TransferProcessed;
						$this.modal.showInvalidTransfer	= result.ShowInvalidTransfer;
						$this.modal.showGenericError	= result.ShowGenericError;
					}, function() {
						$this.modal.showGenericError 	= true;
					});

				promise["finally"](function() {
					$scope.form.$submitting = false;
				});
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcAddCoinToExistingSets", [
		"urlService", "coinService", 
		function(urlService, coinService) {
			return {
				templateUrl	: "template-cache/add-coin/add-coin-to-existing-sets.html",
				replace		: true,
				scope		: {
					data: "=ngcAddCoinToExistingSets"
				},
				controller: [
					"$scope", "setService", "user",
					function($scope, setService, user) {
						$scope.user = user;
						$scope.events = $scope.events || {};
						$scope.isCreatingNewSet = false;

						angular.extend($scope.events, {
							isSelected: function(set) {
								return $scope.selectedSets.indexOf(set) >= 0;
							},
							isDuplicateSetType: function(set) {
								var setTypeSelected = _.filter($scope.selectedSets, { SetTypeID: set.SetTypeID }).length == 1;
								return setTypeSelected && !$scope.events.isSelected(set);
							},
							isValid: function() {
								return $scope.selectedSets.length > 0;
							},
							toggleSet: function(set) {
								if(!set.AddToSetEnabled || $scope.events.isDuplicateSetType(set))
									return;

								$scope.isCreatingNewSet = false;
								if($scope.events.isSelected(set)) {
									$scope.selectedSets.splice($scope.selectedSets.indexOf(set), 1);
								} else {
									$scope.selectedSets.push(set);
								}
							},
							selectCreateNewSet: function() {
								$scope.selectedSets = [];
								$scope.isCreatingNewSet = !$scope.isCreatingNewSet;
							},
							addToSets: function(e) {
								if($scope.selectedSets.length === 0 && !$scope.isCreatingNewSet)
									return;
								
								if($scope.isCreatingNewSet) {
									$scope.$emit("addCoin.selectSetType", $scope.CoinSummary.PeopleCoinID); 
								} else {
									addToSets();
								}
							},
							close: function() {
								$scope.$emit("addCoin.close");
							},
							startOver: function() 		{ 
								$scope.$emit("addCoin.openLookup"); 
							},
							addToAnotherSet: function() { 
								$scope.$emit("addCoin.selectExistingSets", $scope.CoinSummary.PeopleCoinID);
							},
						});

						function addToSets() {
							$scope.form.$submitting = true;
							var addToSetsData = $scope.selectedSets.map(function (set) {
								return {
									peopleCoinID: $scope.CoinSummary.PeopleCoinID,
									peopleSetID: set.PeopleSetID,
									slotID: set.PreferredSlotID
								};
							});
							var promise = setService
								.addToSets(addToSetsData)
								.then(function(results) {
									$scope.results = results;
									$scope.success = results.reduce(function(prev, result) {
										return result.Success && prev;
									});
								}, function() {
									// TODO: Error handling
								});

							promise["finally"](function() {
								$scope.form.$submitting = false;
							});
						}
					}
				],
				link: function(scope, element, attrs, controller) {
					scope.baseUrl = urlService.root;
					scope.data.CoinSummary.DisplayGrade = coinService.replaceGradeIcons(scope.data.CoinSummary.DisplayGrade);
					angular.extend(scope, scope.data);
					scope.selectedSets = [];
					scope.$watch(function() { return scope.data; }, function(newValue) {
						newValue.CoinSummary.DisplayGrade = coinService.replaceGradeIcons(newValue.CoinSummary.DisplayGrade);
						angular.extend(scope, newValue);
						scope.results = null;
						scope.selectedSets = [];
						scope.form.$setPristine();
					});
				}
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcAddCoinToSetType", [
		"urlService", "coinService",
		function(urlService, coinService) {
			return {
				templateUrl	: "template-cache/add-coin/add-coin-to-set-type.html",
				replace		: true,
				scope		: {
					data: "=ngcAddCoinToSetType"
				},
				controllerAs: "ctrl",
				controller: [
					"$scope", "$rootScope",
					function($scope, $rootScope) {
						this.mode = "InvalidCoin";
						this.submitting = false;

						this.addToSetType = function(setType) {
							if(!setType.AddToSetTypeEnabled)
								return;

							this.submitting = true;
							setType.selected = true;
							$scope.$emit("addCoin.setTypeSelected", $scope.CoinSummary.PeopleCoinID, setType.SetTypeID);
						};
						this.requestNewSetType = function() {
							$scope.$emit("addCoin.requestNewSetType", $scope.CoinSummary);
						};
						this.startOver = function() {
							$scope.$emit("addCoin.openLookup");
						};
					}
				],
				link: function(scope, element, attrs, ctrl) {
					if(!scope.data)
						return;

					var setTypes = _(scope.data.Categories)
									.map("SetTypes")
									.flatten()
									.valueOf();

					scope.data.CoinSummary.DisplayGrade = coinService.replaceGradeIcons(scope.data.CoinSummary.DisplayGrade);
					angular.extend(ctrl, scope.data);
					scope.CoinSummary 	= scope.data.CoinSummary;
					ctrl.isPCGSCoin     = (scope.CoinSummary.Grader === "PCGS");
					ctrl.mode 			= (setTypes.length > 0) ? "SelectSetType" : "NoSetTypes";
					ctrl.hasAvailable 	= _.some(setTypes, function(setType) {
						return setType.AddToSetTypeEnabled;
					});
				}
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcAddCoinToSet", [
		"urlService", "coinService",
		function(urlService, coinService) {
			return {
				templateUrl:"template-cache/add-coin/add-coin-to-set.html",
				replace		: true,
				scope		: {
					data: "=ngcAddCoinToSet",
					selectSlot: "=selectSlot",
				},
				controller: [
					"$scope", "setService", "user",
					function($scope, setService, user) {
						$scope.user = user;
						$scope.events = $scope.events || {};

						angular.extend($scope.events, {
							selectSet: function(set) {
								$scope.form.setName.$setValidity("unique", true);
								$scope.selectedSet = set;
								$scope.events.select(set);
							},
							toggleSet: function(set) {
								if($scope.selectedSet && $scope.selectedSet.IsNewSet)
									$scope.selectedSet.SetName = "";

								$scope.events.toggle(set);
								if($scope.events.isSelected(set)) {
									$scope.selectedSet = set;
								} else {
									$scope.selectedSet = null;
								}
							},
							addToSet: function(e) {
								if($scope.selectedSet == null)
									return;

								addToSet();
							},
							close: function() {
								$scope.$emit("addCoin.close");
							},
							goBack: function() {
								if($scope.selectSlot)
									$scope.$emit("addCoin.requestNewSetType", $scope.CoinSummary);
								else $scope.$emit("addCoin.selectSetType", $scope.CoinSummary.PeopleCoinID);
							},
							startOver: function() { 
								$scope.$emit("addCoin.openLookup"); 
							},
							addToAnotherSet: function() { 
								$scope.$emit("addCoin.selectSetType", $scope.CoinSummary.PeopleCoinID); 
							}
						});

						function handleError() {
							$scope.showErrorMessage = true;
						}

						function addToSet() {
							$scope.form.$submitting = true;

							if($scope.selectSlot) {
								if($scope.selectedSet.PeopleSetID) {
									$scope.$emit("addCoin.requestNewSlot", $scope.CoinSummary, $scope.selectedSet.SetTypeID, $scope.selectedSet.PeopleSetID);
								} else {
									setService.createSet($scope.selectedSet.SetTypeID, $scope.selectedSet.SetName)
										.then(function(result) {
											if(result.Success)
												$scope.$emit("addCoin.requestNewSlot", $scope.CoinSummary, $scope.selectedSet.SetTypeID, result.PeopleSetID);
											else handleError();
										})
										.catch(handleError);
								}
							} else {
								var slot = {
									peopleSetID	: $scope.selectedSet.PeopleSetID,
									setName		: $scope.selectedSet.SetName,
									slotID		: $scope.SlotID
								};

								setService
									.addToSet($scope.CoinSummary.PeopleCoinID, slot)
									.then(function(results) {
										$scope.results = results;
									})
									.catch(handleError)
									.finally(function() {
										$scope.form.$submitting = false;
									});
							}
						}
					}
				],
				link: function(scope, element, attrs, controller) {
					scope.baseUrl = urlService.root;
					scope.data.CoinSummary.DisplayGrade = coinService.replaceGradeIcons(scope.data.CoinSummary.DisplayGrade);
					angular.extend(scope, scope.data);
				}
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcApplyImagesForSet", [
		"urlService",
		function(urlService) {
			return {
				scope: {
					showModal: "=ngcApplyImagesForSet"
				},
				templateUrl: urlService.templateUrl("registry/manage/sets/templates/apply-ngc-images/"),
				controllerAs: "ctrl",
				controller: [
					"$scope", "$window", "setService",
					function($scope, $window, setService) {
						var ctrl = this;
						this.applyNGCImages = function(e) {
							$scope.form.$submitting = true;
							var promise = setService
								.applyNGCImagesToSet(ctrl.peopleSetID);

							promise["catch"](handleError);
							ctrl.results = true;
						};
						this.reset = function(e) {
							$scope.form.$submitting = false;
							ctrl.results = false;
							ctrl.imageError = null;
						}
						function handleError(message) {
							if(!message)
								return;
							ctrl.errorMessage = message;
						}

					}
				],
				link: function(scope, element, attrs, ctrl) {
					ctrl.peopleSetID = parseInt(attrs.setId);
				}
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcConfirmSlot", [
		"urlService", "setService", "coinService",
		function(urlService, setService, coinService) {
			return {
				templateUrl: "template-cache/add-coin/confirm-slot.html",
				replace: true,
				scope: {
					slotResults: "=ngcConfirmSlot",
					preferredSlot: "="
				},
				controllerAs: "ctrl",
				controller: [
					"$scope",
					function($scope) {
						var ctrl = this;

						ctrl.confirmSlot = function() {
							addToPreferredSlot();
						};

						ctrl.goToAddToSets = function(results) {
							$scope.$emit("addCoin.selectExistingSets", $scope.slotResults.CoinSummary.PeopleCoinID, true);
						};

						// Duplicated from add-coin.js, refactor when possible
						function addToPreferredSlot() {
							var promise = setService
								.addToSet($scope.slotResults.CoinSummary.PeopleCoinID, $scope.preferredSlot)
								.then(function(results) {
									if(results.Message) {
										ctrl.message = results.Message;
										ctrl.viewMode = null;
										ctrl.showWarning = false;
									}
									else handleError();
								}, handleError);

							promise["catch"](handleError);
						}
						function handleError() {
							// TODO
						}
					}
				],
				link: function(scope, element, attrs, ctrl) {
					scope.CoinSummary = scope.slotResults.CoinSummary;
					scope.CoinSummary.DisplayGrade = coinService.replaceGradeIcons(scope.CoinSummary.DisplayGrade);
					ctrl.message = scope.slotResults.Message;
					ctrl.viewMode = "confirm-slot";
					ctrl.showWarning = true;
				}
			}
		}
]);;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcDeleteSet", [
		"urlService",
		function(urlService) {
			return {
				scope: {
					showModal: "=ngcDeleteSet"
				},
				templateUrl: urlService.templateUrl("registry/manage/sets/templates/delete-set/"),
				controllerAs: "ctrl",
				controller: [
					"$scope", "$window", "setService", "user",
					function($scope, $window, setService, user) {
						var $this = this;

						this.deleteSet = function(e) {
							$scope.form.$submitting = true;

							var promise = setService
								.deleteSet($this.peopleSetID, $this.removeCoins)
								.then(function(success) {
									location.href = urlService.root + "profile/" + user.PeopleID + "/sets/";
								}, handleErrors);
						};

						function handleErrors(error) {
							$scope.form.$submitting = false;
						};
					}
				],
				link: function(scope, element, attrs, ctrl) {
					ctrl.peopleSetID = parseInt(attrs.setId);
				}
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcEditSlot", [
		"urlService",
		function(urlService) {
			return {
				templateUrl	: "template-cache/add-coin/edit-slot.html",
				replace		: true,
				scope		: {
					slot: "=ngcEditSlot"
				},
				controllerAs: "ctrl",
				controller: [
					"$scope", "setService", "coinService",
					function($scope, setService, coinService) {
						var ctrl = this;

						this.selectCoin = function(coin) {
							if((coin.IsInRegistry && !coin.IsVerified) || coin.disabledDuringAdd)
								return;

							$scope.events.toggle(coin);
							if(coin.IsInSlot) {
								if($scope.events.isSelected(coin))
									this.changeRemoveMode(null, coin, null);
								else this.changeRemoveMode(null, coin, "removal-scope");
							} else if(coin.IsPending) {
								ctrl.pendingCoin = coin;
								ctrl.pendingCoin.JustAdded = false;
							} else if(coin.IsRegisteredToOther) {
								ctrl.coinToTransfer = coin;
								ctrl.coinToTransfer.transferMode = "transfer-scope";
							} else {
								coin.addingToSet = true;
								toggleModeForOtherCoins(coin, true);
								setService.addToSet(coin.PeopleCoinID, ctrl.slot, coin.CollectibleID)
									.then(function(results) {
										if(results.NotEligible) {
											ctrl.notEligibleCoin = coin;
										} else if(results.AddToPendingCoins) {
											ctrl.pendingCoin = coin;
											ctrl.pendingCoin.JustAdded = true;
										}
										else
											$scope.$emit("addCoin.close");

										coin.addingToSet = false;
										toggleModeForOtherCoins(coin, false);
									});
							}
						};

						function toggleModeForOtherCoins(coinBeingAdded, disabled){
							_.forEach(ctrl.coins, function(coin) {
								if(coin.IsInRegistry && (coinBeingAdded.PeopleCoinID == coin.PeopleCoinID) || !coin.IsInRegistry && (coinBeingAdded.CollectibleID == coin.CollectibleID))
									return;

								if(disabled && (coin.IsVerified || !coin.IsInRegistry)) 
									coin.disabledDuringAdd = true;
								else if(!disabled && coin.disabledDuringAdd) 
									coin.disabledDuringAdd = false;
							});
						};

						this.requestTransfer = function(e) {
							e.stopPropagation();
							setService
								.requestTransfer(ctrl.coinToTransfer.CollectibleID, ctrl.slot)
								.then(function(results) {
									if(results) {
										ctrl.coinToTransfer.transferMode = "tranfer-request-success";
									}
								});
						};
						this.cancelTransfer = function(e) {
							if(e) e.stopPropagation();
							ctrl.coinToTransfer = null;
							//reset the selectedCoin
							var selectedCoin = _.find(ctrl.coins, { IsInSlot: true });
							$scope.events.select(selectedCoin);
						};

						this.changeRemoveMode = function(e, coin, mode) {
							if(e) e.stopPropagation();
							coin.removeMode = mode;
						};
						this.removeFromSet = function(e, coin) {
							e.stopPropagation();
							setService.removeFromSet(coin.PeopleCoinID, ctrl.slot)
							this.close();
						};
						this.removeFromInventory = function(e, coin) {
							e.stopPropagation();
							setService.removeFromInventory(coin.PeopleCoinID, ctrl.slot)
							this.close();
						};

						this.addNewCoin = function() {
							$scope.$emit("addCoin.openLookup", { slot: ctrl.slot });
						};

						this.close = function() {
							$scope.$emit("addCoin.close");
						};

						this.replaceGradeIcons = coinService.replaceGradeIcons;
					}
				],
				link: function(scope, element, attrs, controller) {
					controller.coins = scope.slot.Coins;
					controller.slot = {
						peopleSetID	 : scope.slot.PeopleSetID,
						slotID		 : scope.slot.SlotID,
						slotName	 : scope.slot.SlotName,
						hasCoins	 : scope.slot.Coins.length > 0,
						isUSCategory : scope.slot.IsUSCategory
					};
					controller.coinToTransfer = null;
					var selectedCoin = _.find(controller.coins, { IsInSlot: true });
					scope.events.select(selectedCoin);
				}
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcModifySet", [
		"urlService",
		function(urlService) {
			return {
				scope: {
					showModal: "=ngcModifySet",
					existingUserSets: "=setsList"
				},
				templateUrl	: function(elem, attrs) {
					var templatePath = "registry/manage/sets/templates/";
					if(parseInt(attrs.setId))
						templatePath += "modify-set/" + attrs.setId + "/";
					else templatePath += "create-set/";

					return urlService.templateUrl(templatePath);
				},
				controllerAs: "ctrl",
				controller: [
					"$scope", "$window", "setService",
					function($scope, $window, setService) {
						var $this = this;

						this.save = function() {
							$scope.form.$submitting = true;

							if($this.isExistingSet) {
								setService.updateSet($this.peopleSetID, $this.setName, $this.setDescription, $this.isObscured)
									.then(function(data) {
										if(data.Success)
											$scope.modal.close();
										else handleError();
									}, handleError);
							} else {
								setService.createSet($this.setTypeID, $this.setName, $this.setDescription, $this.isObscured)
									.then(function(data) {
										if(data.Success)
											$window.location.href = urlService.registrySet(data.PeopleSetID);
										else handleError();
									}, handleError);
							}
						};
						this.reset = function() {
							$this.showUnknownError = false;
							$this.selectSetEnabled = false;
							$this.showSelectSet = false;
							$scope.form.$submitting = false;
						};
						this.goToSetDetail = function(peopleSetID) {
							$scope.modal.close();
							$window.location.href = urlService.registrySet(peopleSetID);
						};

						function handleError(error) {
							$this.showUnknownError = true;
							$scope.form.$submitting = false;
						}
					}
				],
				link: function(scope, element, attrs, ctrl) {
					ctrl.setTypeID		= parseInt(attrs.setTypeId);
					ctrl.setTypeName	= attrs.setTypeName;
					ctrl.peopleSetID	= parseInt(attrs.setId);
					ctrl.isExistingSet	= !isNaN(ctrl.peopleSetID);
					
					scope.$watch("existingUserSets", function(newValue) {
						var hasSetInSetType   = scope.existingUserSets && scope.existingUserSets.length > 0
						ctrl.selectSetEnabled = hasSetInSetType;
						ctrl.showSelectSet	  = hasSetInSetType;
					});
				}
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcRequestNewSetType", [
		"urlService", "coinService", "supportService",
		function(urlService, coinService, supportService) {
			return {
				templateUrl	: "template-cache/add-coin/request-new-set-type.html",
				replace		: true,
				scope		: {
					coinSummary: "=ngcRequestNewSetType"
				},
				controllerAs: "$ctrl",
				controller: [
					"$scope", "$rootScope",
					function($scope, $rootScope) {
						var $ctrl = this;
						$ctrl.requestSetType = false;
						$scope.CoinSummary = $scope.coinSummary;
						$scope.CoinSummary.DisplayGrade = coinService.replaceGradeIcons($scope.CoinSummary.DisplayGrade);

						$ctrl.countryID = null;
						$ctrl.categoryID = null;
						$ctrl.subcategoryID = null;
						$ctrl.setTypeID = null;
						$ctrl.categories = null;
						$ctrl.subcategories = null;
						$ctrl.settypes = null;

						coinService.fetchCountries($scope.CoinSummary.Grader)
						.then(function(countries) {
							$ctrl.countries = countries;
						});

						function loadCategories(countryID) {
							if(countryID == undefined)
								return;

							$ctrl.subcategories = null;
							$ctrl.settypes = null;
							var isPCGS = $scope.CoinSummary.Grader === "PCGS";
							var excludeCirculatedOnly = $scope.CoinSummary.NumericGrade > 58;
							coinService.fetchCategoriesForCountry(countryID, isPCGS, excludeCirculatedOnly)
								.then(function(categories) {
									$ctrl.categories = categories;
									if($ctrl.categories.length === 1) {
										$ctrl.categoryID = $ctrl.categories[0].ID;
										loadSubcategories($ctrl.countryID, $ctrl.categoryID);
									}
								});
						}

						function loadSubcategories(countryID, categoryID) {
							if(countryID == undefined || categoryID == undefined)
								return;

							$ctrl.settypes = null;
							coinService.fetchSubcategoriesForCategory(countryID, categoryID)
								.then(function(subcategories) {
									$ctrl.subcategories = subcategories;
									if($ctrl.subcategories.length === 1) {
										$ctrl.subcategoryID = $ctrl.subcategories[0].ID;
										loadSetTypes(countryID, categoryID, $ctrl.subcategoryID);
									}
								});
						}

						function loadSetTypes(countryID, categoryID, subcategoryID) {
							if(countryID == undefined || categoryID == undefined || subcategoryID == undefined)
								return;

							coinService.fetchSetTypesForSubcategory(countryID, categoryID, subcategoryID, $scope.CoinSummary.PeopleCoinID)
								.then(function(settypes) {
									$ctrl.settypes = settypes;
								});
						}

						$ctrl.resetForm = function(form) {
							$ctrl.requestSetType = false;
							form.$setPristine();
							form.$setUntouched();
							grecaptcha.reset($scope.recaptchaWidgetId);
						};

						$ctrl.countryChanged = function() {
							loadCategories($ctrl.countryID);
							$ctrl.subcategories = null;
							$ctrl.settypes = null;
						};

						$ctrl.categoryChanged = function() {
							loadSubcategories($ctrl.countryID, $ctrl.categoryID);
							$ctrl.subcategories = null;
							$ctrl.settypes = null;
						};

						$ctrl.subcategoryChanged = function() {
							loadSetTypes($ctrl.countryID, $ctrl.categoryID, $ctrl.subcategoryID);
						};

						$ctrl.setTypeSelected = function() {
							if($ctrl.setTypeID === -1)
								$ctrl.requestSetType = true;
							else
								$scope.$emit("addCoin.setTypeSelected", $scope.CoinSummary.PeopleCoinID, $ctrl.setTypeID, true); 
						};

						$ctrl.submitNewSetTypeRequest = function(form){
							form.$submitting = true;

							var contactData = {
								SourceUrl   : urlService.root + urlService.baseUrl,
								FirstName   : $ctrl.UserFirstName,
								EmailAddress: $ctrl.UserEmail,
								Message     : $ctrl.RequestNewSetTypeMessage,
								CertNumber  : $scope.CoinSummary.CertNumber,
								CoinID      : $scope.CoinSummary.CoinID,
								Grade       : $scope.CoinSummary.DisplayGrade,
								ItemDescription : $scope.CoinSummary.ItemDescription
							};

							if(CCG.showRecaptcha)
								contactData["g-recaptcha-response"] = grecaptcha.getResponse($scope.recaptchaWidgetId);

							supportService.sendNewSetTypeRequest(contactData)
								.then(function(data) {
									$ctrl.requestNewSetTypeSuccess = data.Success;
									$ctrl.requestNewSetTypeError   = false;
								})
								.catch(function() {
									$ctrl.requestNewSetTypeSuccess = false;
									$ctrl.requestNewSetTypeError   = true;
								})
								.finally(function() {
									form.$submitting = false;
								});
						};

						$ctrl.goBack = function() {
							$scope.$emit("addCoin.selectSetType", $scope.CoinSummary.PeopleCoinID); 
						};

						$ctrl.goToCoinLookup = function() {
							$scope.$emit("addCoin.openLookup");
						};

						$scope.$on("recaptcha.ready", function() {
							if($("#request_new_set_type_captcha").length)
								$scope.recaptchaWidgetId = grecaptcha.render("request_new_set_type_captcha", { "sitekey": CCG.recaptchaSiteKey });
						});
					}
				]
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcRequestNewSlot", [
		"urlService", "coinService", "setService",
		function(urlService, coinService, setService) {
			return {
				templateUrl	: "template-cache/add-coin/request-new-slot.html",
				replace		: true,
				scope		: {
					coinSummary: "=ngcRequestNewSlot",
					setTypeId:   "=setTypeId",
					peopleSetId: "=peopleSetId",
				},
				controllerAs: "$ctrl",
				controller: [
					"$scope", "$rootScope",
					function($scope, $rootScope) {
						var $ctrl = this;
						$scope.CoinSummary = $scope.coinSummary;
						$scope.CoinSummary.DisplayGrade = coinService.replaceGradeIcons($scope.CoinSummary.DisplayGrade);
						setService.fetchSlotsForSetType($scope.setTypeId)
						.then(function(slots) {
							$ctrl.slots = slots;
						});

						function handleError() {
							//todo
						}

						$ctrl.slotSelected = function() {
							var slot = {
									peopleSetID	   : $scope.peopleSetId,
									setName		   : '', //don't need this
									slotID		   : $ctrl.slotID,
									requestNewSlot : true
								};

							setService
								.addToSet($scope.CoinSummary.PeopleCoinID, slot)
								.then(function(results) {
									$scope.results = results;
								})
								.catch(handleError)
								.finally(function() {
									$scope.form.$submitting = false;
								});
						};

						$ctrl.goBack = function() {
							$scope.$emit("addCoin.setTypeSelected", $scope.CoinSummary.PeopleCoinID, $scope.setTypeId, true); 
						};

						$ctrl.addMoreCoins = function() {
							$scope.$emit("addCoin.openLookup"); 
						};
					}
				]
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("setService", [
		"webServiceFactory", "$rootScope", "storageService", "Upload",
		function(webServiceFactory, $rootScope, storageService, Upload) {
			return webServiceFactory.create({
				manageSetPath: function(setID) {
					return "registry/manage/sets/" + setID + "/";
				},
				getSet: function(peopleSetID) {
					return this.get(this.manageSetPath(peopleSetID), {}, false);
				},
				autoBuildSet: function(peopleSetID) {
					return this.post(this.manageSetPath(peopleSetID) + "auto-build/");
				},
				createSet: function(setTypeID, setName, setDescription, isObscured) {
					return this.post(this.manageSetPath("create"), {
						setTypeID		: setTypeID,
						setName			: setName,
						setDescription	: setDescription,
						isObscured		: isObscured
					});
				},
				updateSet: function(peopleSetID, setName, setDescription, isObscured) {
					var updates = {
						peopleSetID		: peopleSetID,
						setName			: setName,
						setDescription	: setDescription,
						isObscured		: isObscured
					};

					return this.post(this.manageSetPath(peopleSetID), updates)
						.then(function(results) {
							$rootScope.$broadcast("set.refresh");
							$rootScope.$broadcast("user.refresh");
							return results;
						});
				},
				applyNGCImagesToSet: function(peopleSetID) {
					return this.post("resources/services/registry/apply-ngc-images-to-set/", {
						peopleSetID: peopleSetID
					});
				},
				deleteSet: function(peopleSetID, removeCoins) {
					return this.post(this.manageSetPath(peopleSetID) + "delete/", {
						peopleSetID: peopleSetID,
						removeCoins: removeCoins
					});
				},
				fetchAvailableSets: function(peopleCoinID, setTypeID, fetchAllSets) {
					return this.post("registry/manage/sets/fetch-available-sets/", {
						peopleCoinID: peopleCoinID,
						setTypeID:    setTypeID,
						fetchAllSets: fetchAllSets
					});
				},
				fetchSetsBySetType: function(setTypeID, page) {
					return this.get("resources/services/registry/setTypes/" + setTypeID + "/sets/" + page + "/");
				},
				fetchSetHistory: function(peopleSetID) {
					return this.get("registry/competitive-sets/" + peopleSetID + "/set-history/");
				},
				fetchAvailableSetTypes: function(peopleCoinID) {
					return this.post("registry/manage/sets/fetch-available-set-types/", {
						peopleCoinID: peopleCoinID
					});
				},
				fetchAvailableCoins: function(slot) {
					return this.post("registry/manage/sets/fetch-available-coins/", {
						peopleSetID: slot.peopleSetID,
						slotID: slot.slotID
					});
				},
				fetchCoinScoresForSlot: function(slotID, filters) {
					return this.get("resources/services/registry/competitive/slots/" + slotID + "/scores/", {
						greenCAC: filters.greenCAC,
						goldCAC: filters.goldCAC,
						page: filters.page
					});
				},
				fetchCoinScoresForAllSlots(setTypID, filters) {
					return this.get("resources/services/registry/competitive/settypes/" + setTypID + "/scores/", {
						greenCAC: filters.greenCAC,
						goldCAC: filters.goldCAC,
						page: filters.page
					});
				},
				fetchCoinsForSlot: function(slotID) {
					return this.get("/resources/services/registry/competitive/slots/" + slotID + "/coins/");
				},
				fetchSlotsForSetType: function(setTypeID) {
					var url = "resources/services/registry/competitive/settypes/" + setTypeID + "/slots/";
					return this.get(url);
				},
				fetchSlotsForSet: function(peopleSetID) {
					var url = "resources/services/registry/competitive/sets/" + peopleSetID + "/slots/";
					return this.get(url);
				},
				fetchSlotsForAwardSet: function(awardSetID) {
					var url = "resources/services/registry/competitive/award-sets/" + awardSetID + "/slots/";
					return this.get(url);
				},
				fetchNominationData: function(peopleSetID) {
					return this.get("registry/competitive-sets/" + peopleSetID + "/nominations/");
				},
				saveNomination: function(peopleSetID, nomination) {
					var url = "registry/competitive-sets/" + peopleSetID + "/save-nomination/" + nomination.awardTypeID + "/";
					var data = { nominationComments: nomination.comments };
					return this.post(url, data);
				},
				validateSlot: function(peopleCoinID, slotData) {
					return this.post("registry/manage/sets/validate-add-to-slot/", {
						peopleCoinID: peopleCoinID,
						peopleSetID	: slotData.peopleSetID,
						slotID		: slotData.slotID
					}).then(this.refreshSlot);
				},
				requestTransfer: function(collectibleID, slotData) {
					return this.post("registry/manage/sets/request-transfer/", {
						collectibleID	: collectibleID,
						peopleSetID		: slotData.peopleSetID,
						slotID			: slotData.slotID,
						setName			: slotData.setName
					});
				},
				addToSet: function(peopleCoinID, slotData, collectibleID) {
					return this.post("registry/manage/sets/add-to-set/", {
							peopleCoinID	: peopleCoinID,
							collectibleID	: collectibleID,
							peopleSetID		: slotData.peopleSetID,
							slotID			: slotData.slotID,
							setName			: slotData.setName,
							requestNewSlot  : slotData.requestNewSlot,
						})
						.then(function(results) {
							if(!results.AddToPendingCoins) {
								$rootScope.$broadcast("set.refresh", results);
								$rootScope.$broadcast("user.refresh");
							}
							return results;
						});
				},
				addToSets: function(addToSetsData) {
					return this.post("registry/manage/sets/add-to-sets/", addToSetsData)
						.then(this.refreshSlot);
				},
				removeFromSet: function(peopleCoinID, slotData) {
					return this.post("registry/manage/sets/remove-from-set/", {
						peopleCoinID: peopleCoinID,
						peopleSetID	: slotData.peopleSetID,
						slotID		: slotData.slotID
					}).then(this.refreshSlot);
				},
				removeFromInventory: function(peopleCoinID, slotData) {
					return this.post("registry/manage/sets/remove-from-inventory", {
						peopleCoinID: peopleCoinID,
						peopleSetID: slotData.peopleSetID,
						slotID: slotData.slotID
					}).then(this.refreshSlot);
				},
				getCoinInSlot: function(peopleSetID, slotID) {
					return this.get("registry/manage/sets/" + peopleSetID + "/slot/" + slotID + "/");
				},
				refreshSlot: function(modifySetResults) {
					$rootScope.$broadcast("set.refresh", modifySetResults);
					$rootScope.$broadcast("user.refresh");
					return modifySetResults;
				},
				deleteSetCoverImage: function(peopleSetID) {
					return this.post("registry/manage/sets/" + peopleSetID + "/cover-image/delete/");
				},
				updateSetCoverImage: function(peopleSetID, file, orientation) {
					return Upload.upload({ url: "/registry/manage/sets/" + peopleSetID + "/cover-image/", data: { image: file, orientation: orientation } })
						.then(function(results) {
							return results.data;
						});
				},
				requestScoreCorrection: function(setTypeID, selectedSlotCoins, comments) {
					return this.post("/registry/request-score-correction/", {
						comments: comments,
						setTypeID: setTypeID,
						selectedSlotCoins: selectedSlotCoins,
					});
				}
			});
		}
	]);
;
"use strict";
angular.module("CCG.Shared")
	.directive("ngcCounterfeitedCoins", [
		"urlService",
		function(urlService) {
			return {
				templateUrl: urlService.templateUrl("resources/counterfeit-detection/partials/couterfeited-coins/"),
				controllerAs: "counterfeitedCoinsCtrl",
				controller: [
					"$scope", "$window", "urlService", "counterfeitedCoinsData",
					function($scope, $window, urlService, counterfeitedCoinsData) {
						var self = this;
						angular.extend(self, counterfeitedCoinsData);

						var baseUrl = urlService.fullPath().replace(/\/(\d+)\/$/, "/");
						updateNavigation();

						self.next = function() {
							goToCoin(self.selectedCoin.SortOrder + 1);
						};
						self.previous = function() {
							goToCoin(self.selectedCoin.SortOrder - 1);
						};
						// This triggers when the user hits the browser back / forward buttons and the history has been updated via javascript.
						angular.element($window).on("popstate", function() {
							loadCoin();
							$scope.$apply();
						});

						function goToCoin(targetCoinSort) {
							urlService.updateUrl(baseUrl + targetCoinSort + "/");
							loadCoin();
							$("html, body").animate({ scrollTop: 0 }, "fast");
						}
						function loadCoin() {
							var match = urlService.fullPath().match(/\/(\d+)\/$/);
							if(!match)
								return;

							var targetCoinSort = parseInt(match[1]);
							var targetCoin = _.find(self.coinsData, { SortOrder: targetCoinSort });
							if(!targetCoin)
								return;

							self.selectedCoin      = targetCoin;
							$window.document.title = self.selectedCoin.SortOrder + ". " + self.selectedCoin.Name + " | NGC";
							$window.document.getElementsByTagName('meta')["description"].content = self.selectedCoin.MetaDescription;
							updateNavigation();
						}
						function updateNavigation() {
							var targetCoinSort   = self.selectedCoin.SortOrder;
							self.hasPreviousCoin = _.some(self.coinsData, { SortOrder: targetCoinSort - 1 });
							self.hasNextCoin     = _.some(self.coinsData, { SortOrder: targetCoinSort + 1 });
						}
					}
				]
			}
	}]);
;
"use strict";
angular.module("CCG.Shared")
	.directive("ngcSrc", function() {
		return {
			link: function(scope, element, attrs) {
				loadImage();

				scope.$watch(function() {
						return attrs.ngcSrc;
					},
					function(newVal, oldVal) {
						if(oldVal !== newVal) {
							loadImage();
						}
					});

				function loadImage() {
					var img = new Image();
					img.src = attrs.ngcSrc;

					$(element[0]).parent().addClass("loading");

					img.onload = function() {
						$(element[0]).parent().removeClass("loading");
						element[0].src = attrs.ngcSrc;
					};
				};
			}
		};
	});
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcContactSupport", [
		"supportService",
		function(supportService) {
			return {
				scope: true,
				controllerAs: "contact",
				controller: [
					"$scope", "$rootScope", "$timeout",
					function($scope, $rootScope, $timeout) {
						var $this = this;

						this.showContactForm    = true;
						this.showSuccessMessage = false;
						this.showErrorMessage   = false;

						this.resetForm = function() {
							$this.showContactForm    = true;
							$this.showSuccessMessage = false;
							$this.showErrorMessage   = false;
							$this.Message            = "";
							$this.coinInfo           = undefined;
							$scope.contactForm.reset();
							grecaptcha.reset($scope.recaptchaWidgetId);
						};
						this.submit = function() {
							$scope.contactForm.$submitting = true;

							var contactData = {
								FirstName   : $this.FirstName,
								EmailAddress: $this.EmailAddress,
								Message     : $this.Message,
							};

							if($this.coinInfo) {
								contactData.CertNumber      = $this.coinInfo.certNumber;
								contactData.CoinID          = $this.coinInfo.coinID;
								contactData.ItemDescription = $this.coinInfo.itemDescription;
								contactData.Grade           = $this.coinInfo.grade;
							}

							if(CCG.showRecaptcha)
								contactData["g-recaptcha-response"] = grecaptcha.getResponse($scope.recaptchaWidgetId);

							supportService.sendContactForm(contactData)
								.then(function(data) {
									$this.showContactForm    = false;
									$this.showSuccessMessage = data.Success;
									$this.showErrorMessage   = !data.Success;
								})
								.catch(function() {
									$this.showContactForm  = false;
									$this.showErrorMessage = true;
								})
								.finally(function() {
									$scope.contactForm.$submitting = false;
								});
						};
						this.contactSupport = function(scope, action, coinInfo) {
							// Need to apply here to force update on mobile devices
							$timeout(function() {
								$this.coinInfo         = coinInfo;
								$this.showContactModal = true;
								$rootScope.$broadcast("tooltips.hide");

								$timeout(function() {
									if(angular.element("#registry_support_captcha").length)
										$scope.recaptchaWidgetId = grecaptcha.render("registry_support_captcha", { "sitekey": CCG.recaptchaSiteKey });
								});
							});
						};

						$scope.$on("mxToggle.Modals.contactSupport", this.contactSupport);
					}
				]
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcDisclaimerSupport", [
		"urlService",
		function (urlService) {
			return {
				scope: true,
				controllerAs: "$ctrl",
				controller: [
					"$scope", "$timeout",
					function ($scope, $timeout) {
						var $ctrl = this;
						$ctrl.visible = false;

						$ctrl.openModal = function() {
							$timeout(function () {
								$ctrl.visible = true;
							});
						};

						$scope.$on("mxToggle.Modals.disclaimerSupport", $ctrl.openModal);
						$scope.$watch(function() {
							return new URI().segment(0);
						}, function(view) {
							if(view === "details-census") {
								$ctrl.view = "census";
							} else {
								$ctrl.view = view;
							}
						});
					}
				]
			}
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.directive("ngcReportTransaction", [
		function() {
			return {
				templateUrl: "template-cache/price-guide/report-transaction.html",
				controllerAs: "$ctrl",
				controller: [
					"$scope", "urlService", "supportService",
					function($scope, urlService, supportService) {
						var $ctrl = this;
						var currentUrl = urlService.path();

						$ctrl.formData = {};
						$ctrl.isCensus = _.includes(currentUrl, "census");
						$ctrl.showForm = true;
						$ctrl.showError = false;

						$ctrl.clearForm = function() {
							$ctrl.showError = false;
							$ctrl.showForm = true;
							$ctrl.formData = {};
							$ctrl.reportForm.$setPristine();
							$ctrl.reportForm.$setUntouched();
							grecaptcha.reset($scope.recaptchaWidgetId);
						};

						$ctrl.sendTransactionReport = function() {
							if(CCG.showRecaptcha)
								$ctrl.formData["g-recaptcha-response"] = grecaptcha.getResponse($scope.recaptchaWidgetId);

							supportService.sendTransactionReport($ctrl.formData)
								.then(function(results) {
									$ctrl.showForm = false;
									$ctrl.showError = !results.Success;
								});
						};

						$scope.$on("ccgViewSwitch.viewSwitched", function(e, view) {
							if(_.includes(["census", "price-guide"], view)) {
								$ctrl.isCensus = view === "census";
							}
						});

						$scope.$on("recaptcha.ready", function() {
							if(angular.element("#report_transaction_captcha").length)
								$scope.recaptchaWidgetId = grecaptcha.render("report_transaction_captcha", { "sitekey": CCG.recaptchaSiteKey });
						});
					}
				]
			};
		}
	]);

;
"use strict";
angular.module("NGC.Registry")
	.factory("supportService", [
		"webServiceFactory",
		function(webServiceFactory) {
			return webServiceFactory.create({
				sendContactForm: function(form) {
					return this.post("resources/services/support/contact-us/", form);
				},
				sendNewSetTypeRequest: function(form) {
					return this.post("resources/services/support/request-new-set-type/", form);
				},
				sendTransactionReport: function(form) {
					return this.post("resources/services/support/report-transaction/", form);
				},
			});
		}
	]);
;
"use strict";

angular.module("NGC.Registry")
	.service("ngcHttpCacheService", [
		"$q", "$http",
		function($q, $http) {
			var cache = {};

			return {
				get: function(url) {
					var cached = cache[url];
					if (cached)
						return $q.when(cached);

					return $http.get(url)
						.then(function(response) {
							cache[url] = response.data;
							return cache[url];
						});
				}
			};
		}
	]);;
"use strict";
$(document).ready(function() {
	var isIframe = false;
	try {
		isIframe = window.self !== window.top;
	} catch(e) {
		isIframe = true;
	}
	
	if(isIframe) {
		var cleanLinks = function() {
			var $iframeDisable					= $(".iframe-disable").add(".iframe-disable a");
			var $iframeDisableAndRemoveTarget	= $iframeDisable.add(".iframe-remove-target").add(".iframe-remove-target a");

			$iframeDisable.removeAttr("href");
			$iframeDisableAndRemoveTarget.removeAttr("target");
		};

		var observer = new MutationObserver(cleanLinks);
		observer.observe(document.documentElement, {
			childList: true,
			subtree: true
		});

		cleanLinks();
	}
});
;
"use strict";
angular.module("NGC.Registry")
	.factory("imageResizer", [
		function() {
			var MAX_HEIGHT	= 2448;
			var MAX_WIDTH	= 2448;

			function calculateDimensions(img) {
				var results = {
					height: MAX_HEIGHT,
					width: MAX_WIDTH,
					needsResize: false
				};

				if(img) {
					var width = img.width;
					var height = img.height;
					var needsResize = false;

					if(width >= height) {
						if(width > MAX_WIDTH) {
							width	= MAX_WIDTH;
							height *= MAX_WIDTH / width;
							needsResize = true;
						}
					} else {
						if(height > MAX_HEIGHT) {
							height = MAX_HEIGHT;
							width *= MAX_HEIGHT / height;
							needsResize = true;
						}
					}

					results.width = width;
					results.height = height;
					results.needsResize = needsResize;
				}
				
				return results;
			}
			function dataURLtoBlob(dataURL, type) {
				var parts = dataURL.match(/data:([^;]*)(;base64)?,([0-9A-Za-z+/]+)/);
				var binStr = atob(parts[3]);
				var len = binStr.length;
				var arr = new Uint8Array(len);

				for(var i = 0; i < len; i++) {
					arr[i] = binStr.charCodeAt(i);
				}

				return new Blob([arr], { type: type || "image/png" });
			}
			function resizeImage(file, width, height, callback) {
				var fr = new FileReader();

				fr.onloadend = function() {
					// get EXIF meta data
					var exif = EXIF.readFromBinaryFile(new BinaryFile(this.result));
					
					canvasResize(file, {
						width: width,
						height: height,
						crop: false,
						quality: 90,
						callback: function(data, width, height) {
							callback(dataURLtoBlob(data, file.type), exif.Orientation);
						}
					});
				};
				
				fr.readAsBinaryString(file);
			}

			return {
				resize: function(file, callback) {
					if(file && file.type.match(/image.*/)) {
						var reader = new FileReader();
						reader.onload = function(e) {
							var img = document.createElement("img");
							img.onload = function(e) {
								var results = calculateDimensions(e.target);
								if(results.needsResize)
									resizeImage(file, results.width, results.height, callback);
								else callback(file);
							};
							img.src = e.target.result;
						};
						reader.readAsDataURL(file);
					} else {
						callback(null);
					}
				}
			}
		}
	]);;
"use strict";
angular.module("NGC.Registry")
	.service("metadataService", [
		"$http", "urlService",
		function($http, urlService) {
			var self = this;
			self.updateMetadata = function(key, replacements) {
				// Retrieve server-side metadata
				var url = urlService.templateUrl("resources/services/metadata/" + key + "/");
				return $http.post(url, { replacements: replacements })
					.then(function(response) {
						var metadata = response.data;
						if(!metadata)
							return;

						$("title").text(metadata.title);
						$("meta[name=description]").attr("content", metadata.description);

						if(metadata.canonicalUrl)
							$("link[rel=canonical]").attr("href", metadata.canonicalUrl);

						// Modify hreflang tags
						var currentPath = urlService.path();
						$("link[rel=alternate]").each(function() {
							this.attributes.href.value = new URI(this.attributes.href.value).origin() + currentPath;
						});
					});
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("partialsService", [
		"webServiceFactory",
		function(webServiceFactory) {
			return webServiceFactory.create({
				fetch: function(url, force) {
					return this.get(url, null, force !== true);
				}
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.service("ngcScrollService", [
		"$rootScope", "$window", "ngcWindowService",
		function($rootScope, $window, ngcWindowService) {
			var self = this;

			// == Scroll Events ===========================================================
			// ============================================================================
			// Centralizes and throttles scroll events to avoid slowing down the page when scrolling.
			var previousScroll = ngcWindowService.getViewportScroll();
			var throttledScroll = _.throttle(onScrollThrottled, 100);
			var ngWindow = angular.element($window);
			ngWindow.on("resize scroll mousewheel", onScrollNoDelay);
			ngWindow.on("resize scroll mousewheel", throttledScroll);
			ngWindow.ready(function() {
				onScrollNoDelay();
				throttledScroll();
			});

			var onScrollItems = [];
			var throttledItems, noDelayItems;
			self.registerOnScroll = function(elem, cb, noDelay) {
				onScrollItems.push({
					element: elem,
					callback: cb,
					noDelay: noDelay
				});

				throttledItems = _.filter(onScrollItems, { noDelay: undefined });
				noDelayItems = _.filter(onScrollItems, { noDelay: true });
			};

			self.deregisterOnScroll = function(elem) {
				_.remove(onScrollItems, { element: elem });
			};

			self.getDirection = getDirection;

			function getDirection(newValue, oldValue, positiveLabel, negativeLabel) {
				if (newValue > oldValue) {
					return positiveLabel;
				} else if (newValue < oldValue) {
					return negativeLabel;
				}
			}

			function processScroll(items) {
				var newScroll = ngcWindowService.getViewportScroll();
				newScroll.directionX = getDirection(newScroll.x, previousScroll.x, "right", "left");
				newScroll.directionY = getDirection(newScroll.y, previousScroll.y, "down", "up");

				if (onScrollItems && onScrollItems.length > 0) {
					onScrollItems.forEach(function(item) {
						item.callback(newScroll);
					});
				}

				previousScroll = newScroll;
			}

			function onScrollNoDelay() {
				processScroll(noDelayItems);
			}

			function onScrollThrottled() {
				processScroll(throttledItems);
			}


			// == Scroll to Element =======================================================
			// ============================================================================
			var globalOffset = 0;

			self.performScroll = function(elem, duration, container, offset, overrideOffset) {
				elem = angular.element(elem);
				offset = (overrideOffset) ? offset : globalOffset + offset;

				if (duration) {
					elem.velocity("scroll", {
						duration: duration,
						offset: offset,
						container: container,
					});
				} else {
					var elementOffset = ngcWindowService.getElementOffset(elem);
					(container || $window).scrollTo(0, elementOffset.top - offset);
				}
			};

			// Manages the elements that need to be accounted for when scrolling to an element.
			var globalOffsetElements = [];
			self.registerOffsetElement = function(element) {
				if (_.some(globalOffsetElements, { element: element })) {
					return; // Don't add it if it already is in the array.
				}

				globalOffsetElements.push({
					element: element,
					offset: element[0].offsetHeight
				});
				refreshGlobalOffset();
			};
			self.deregisterOffsetElement = function(element) {
				_.remove(globalOffsetElements, { element: element });
				refreshGlobalOffset();
			};
			self.getGlobalOffset = function(excludeElement) {
				if (excludeElement) {
					return _.sumBy(_.reject(globalOffsetElements, { element: excludeElement }), "offset");
				} else {
					return globalOffset;
				}
			};

			function refreshGlobalOffset() {
				globalOffset = _.sumBy(globalOffsetElements, 'offset') || 0;
			}

		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.service("styleService", [
		function() {
			var _styleData = {};

			function createStylesheet(groupKey) {
				_styleData[groupKey] = {};
				_styleData[groupKey].rules = [];
				_styleData[groupKey].styleNode = document.createElement("style");
				_styleData[groupKey].styleNode.type = "text/css";

				// Webkit workaround
				_styleData[groupKey].styleNode.appendChild(document.createTextNode(""));

				// The stylesheet var must be assigned after the _styleData[groupKey].styleNode is in the document
				document.head.appendChild(_styleData[groupKey].styleNode);
				_styleData[groupKey].stylesheet = _styleData[groupKey].styleNode.sheet;
			}

			return {
				addStyle: function(groupKey, style) {
					if(!_styleData[groupKey] || !_styleData[groupKey].stylesheet) {
						createStylesheet(groupKey);
					}

					// Don't add duplicate styles
					var index = _styleData[groupKey].rules.indexOf(style);
					if(index < 0) {
						index = _styleData[groupKey].rules.push(style) - 1;
						_styleData[groupKey].stylesheet.insertRule(style, index);
					}
				},
				addStyles: function(groupKey, styles) {
					if(!styles || !styles.length)
						return;

					styles.forEach(function(style) {
						this.addStyle(groupKey, style);
					}.bind(this));
				},
				removeStyle: function(groupKey, style) {
					if(!_styleData[groupKey] || !_styleData[groupKey].stylesheet)
						return;

					var index = _styleData[groupKey].rules.indexOf(style);
					if(index) {
						_styleData[groupKey].rules.splice(index, 1);
						_styleData[groupKey].stylesheet.deleteRule(index);
					}
				},
				removeStyles: function(groupKey, styles) {
					if(!styles || !styles.length)
						return;

					styles.forEach(function(style) {
						this.removeStyle(groupKey, style);
					}.bind(this));
				},
				clearStyles: function(groupKey) {
					if(!_styleData[groupKey] || !_styleData[groupKey].stylesheet)
						return;

					$(_styleData[groupKey].styleNode).remove();
					createStylesheet(groupKey);
				}
			};
		}]
	);
;
"use strict";
angular.module("NGC.Registry")
	.service("ngcWindowService", [
		"$window",
		function($window) {
			var self = this;

			self.getViewportScroll = function() {
				return {
					x: $window.pageXOffset,
					y: $window.pageYOffset
				};
			};
			
			self.getElementOffset = function(elem) {
				var ngElem = angular.element(elem);
				var rect = ngElem[0].getBoundingClientRect();
				var scroll = self.getViewportScroll();
				
				return {
					top: rect.top + scroll.y,
					left: rect.left + scroll.x,
					right: rect.right + scroll.x,
					bottom: rect.bottom + scroll.y,
				};
			};
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.factory("varietyPlusService", [
		"webServiceFactory",
		function(webServiceFactory) {
			return webServiceFactory.create({
				getSubcategoryFilters: function(subcategoryID) {
					var url = URI.expand("/variety-plus/data/subcategories/{subcategoryID}/filters/", { subcategoryID: subcategoryID });
					return this.get(url.toString());
				},
				fetchVarietiesForSubcategory: function(subcategoryID, filters, page, showHidden, coinID) {
					var url = URI.expand("/variety-plus/data/subcategories/{subcategoryID}/varieties/", { subcategoryID: subcategoryID })
									.addSearch(filters)
									.addSearch("page", page)
									.addSearch("coinID", coinID)
									.addSearch("showHidden", showHidden);

					return this.get(url.toString());
				},
			});
		}
	]);
;
"use strict";
angular.module("NGC.Registry")
	.controller("varietyPlusSubcategoryController", [
		"$element", "$sce", "$scope", "$location", "urlService", "varietyPlusService",
		function($element, $sce, $scope, $location, urlService, varietyPlusService) {
			var $ctrl = this;

			var baseImageUrl = $element.attr("base-image-url");
			var subcategoryID = $element.attr("subcategory-id");
			var params = (new URI()).search(true);
			$ctrl.requestMintingProcess = params["minting-process"];
			var page = parseInt(params["page"]) || 1;
			$location.search("page", page);

			$ctrl.showMintingProcessSwitch = false;
			$ctrl.varieties  = [];
			$ctrl.startYears = [];
			$ctrl.endYears   = [];
			$ctrl.filters    = {};

			varietyPlusService.getSubcategoryFilters(subcategoryID)
				.then(function(filters) {
					$ctrl.startYears = _.clone(filters.Years);
					$ctrl.endYears   = _.clone(filters.Years);
					$ctrl.showMintingProcessSwitch = (filters.HasMSVarieties && filters.HasPFVarieties);

					$ctrl.filters.startYear      = _.first($ctrl.startYears);
					$ctrl.filters.endYear        = _.last($ctrl.endYears);
					$ctrl.filters.mintingProcess = (filters.HasMSVarieties) ? "MS" : "PF";

					if($ctrl.requestMintingProcess)
						$ctrl.filters.mintingProcess = $ctrl.requestMintingProcess === "MS"? "MS" : "PF";

					var coinID = (new URI()).segment(4);
					populateVarieties(page, coinID);
				});

			$ctrl.changeYear = function() {
				$ctrl.endYears = _.filter($ctrl.startYears, function(year) {
					return year >= parseInt($ctrl.filters.startYear);
				});
				if($ctrl.endYears.indexOf($ctrl.filters.endYear) === -1) {
					$ctrl.filters.endYear = _.first($ctrl.endYears);
				}

				$ctrl.changePage(1);
			};
			$ctrl.changeMintingProcess = function(mintingProcess) {
				$ctrl.filters.mintingProcess = mintingProcess || "MS";
				$ctrl.changePage(1);
			};
			$ctrl.parseExpanderData = function(card, results) {
				results.hasCrossReferences = !_.isEmpty(results.CrossReferences);

				if(results.CensusUrl) {
					results.CensusUrl = results.CensusUrl.replace("~", "");
				}

				_.forEach(results.Images, function(image) {
					image.ImagePath = $sce.trustAsResourceUrl(baseImageUrl + image.ImagePath);
					image.ThumbnailPath = baseImageUrl + image.ThumbnailPath;
				});

				return results;
			};
			$ctrl.changePage = function(page) {
				urlService.replaceUrl(urlService.baseUrl + "?page=" + page);
				populateVarieties(page);
			};

			function populateVarieties(page, coinID) {
				varietyPlusService.fetchVarietiesForSubcategory(subcategoryID, $ctrl.filters, page, false, coinID)
					.then(function(varieties) {
						varieties.Items.forEach(function(variety) {
							variety.url = urlService.baseUrl + variety.CoinID + "/";
							if(variety.ThumbnailPath) {
								variety.imageUrl = baseImageUrl + variety.ThumbnailPath;
							}
						});

						$ctrl.varieties = varieties;
					});
			};
		}
	]);
;
