public void GridWeightsMatter()
        {
            const int    topItemCount    = 3;
            const string tieBreakerInput = "GOLD";
            var          topCandidates   = new TopAddressCandidates(topItemCount, new CandidateComparer(tieBreakerInput));

            topCandidates.Add(new Candidate
            {
                Address = "GOLD",
                Score   = 5,
                Weight  = 100
            });

            topCandidates.Add(new Candidate
            {
                Address = "GOLDS",
                Score   = 5,
                Weight  = 100
            });

            topCandidates.Add(new Candidate
            {
                Address = "BRONZE",
                Score   = 5,
                Weight  = 1
            });

            topCandidates.Add(new Candidate
            {
                Address = "SILVER",
                Score   = 5,
                Weight  = 50
            });

            topCandidates.Add(new Candidate
            {
                Address = "Runner up",
                Score   = 5,
                Weight  = 0
            });

            var items = topCandidates.GetTopItems();

            const int addOneForWinnerWhichIsRemoved = 1;

            Assert.That(items.Count(), Is.EqualTo(topItemCount + addOneForWinnerWhichIsRemoved));
            var candidate = items.First();

            Assert.That(candidate.Score, Is.EqualTo(5));
            Assert.That(candidate.Address, Is.EqualTo("GOLD"));
        }
        public void GridWeightsMatter()
        {
            const int topItemCount = 3;
            const string tieBreakerInput = "GOLD";
            var topCandidates = new TopAddressCandidates(topItemCount, new CandidateComparer(tieBreakerInput));

            topCandidates.Add(new Candidate
                {
                    Address = "GOLD",
                    Score = 5,
                    Weight = 100
                });

            topCandidates.Add(new Candidate
            {
                Address = "GOLDS",
                Score = 5,
                Weight = 100
            });

            topCandidates.Add(new Candidate
                {
                    Address = "BRONZE",
                    Score = 5,
                    Weight = 1
                });

            topCandidates.Add(new Candidate
                {
                    Address = "SILVER",
                    Score = 5,
                    Weight = 50
                });

            topCandidates.Add(new Candidate
                {
                    Address = "Runner up",
                    Score = 5,
                    Weight = 0
                });

            var items = topCandidates.GetTopItems();

            const int addOneForWinnerWhichIsRemoved = 1;

            Assert.That(items.Count(), Is.EqualTo(topItemCount + addOneForWinnerWhichIsRemoved));
            var candidate = items.First();
            Assert.That(candidate.Score, Is.EqualTo(5));
            Assert.That(candidate.Address, Is.EqualTo("GOLD"));
        }
        public void Greater_weight_breaks_score_tie()
        {
            const int    topItemCount    = 3;
            const string tieBreakerInput = "GOLD";
            var          topCandidates   = new TopAddressCandidates(topItemCount, new CandidateComparer(tieBreakerInput));

            topCandidates.Add(new Candidate {
                Address = "GOLD",
                Score   = 5,
                Weight  = 100
            });

            topCandidates.Add(new Candidate {
                Address = "GOLDS",
                Score   = 5,
                Weight  = 99
            });

            topCandidates.Add(new Candidate {
                Address = "BRONZE",
                Score   = 5,
                Weight  = 1
            });

            topCandidates.Add(new Candidate {
                Address = "SILVER",
                Score   = 5,
                Weight  = 50
            });

            topCandidates.Add(new Candidate {
                Address = "Runner up",
                Score   = 5,
                Weight  = 0
            });

            var items     = topCandidates.Get();
            var candidate = items.First();

            const int addOneForWinnerWhichIsRemoved = 1;

            items.Count.ShouldBe(topItemCount + addOneForWinnerWhichIsRemoved);
            candidate.Score.ShouldBe(5);
            candidate.Address.ShouldBe("GOLD");
        }
        public void SizeIsTwoWhenSuggestIsZeroForScoreDifferenceCalculating()
        {
            const int    suggestCount    = 0;
            const string tieBreakerInput = "";
            var          topCandidates   = new TopAddressCandidates(suggestCount, new CandidateComparer(tieBreakerInput));

            topCandidates.Add(new Candidate
            {
                Address = "GOLD",
                Score   = 5,
                Weight  = 100
            });

            topCandidates.Add(new Candidate
            {
                Address = "GOLDS",
                Score   = 5,
                Weight  = 100
            });

            topCandidates.Add(new Candidate
            {
                Address = "BRONZE",
                Score   = 5,
                Weight  = 1
            });

            topCandidates.Add(new Candidate
            {
                Address = "SILVER",
                Score   = 5,
                Weight  = 50
            });

            topCandidates.Add(new Candidate
            {
                Address = "Runner up",
                Score   = 5,
                Weight  = 0
            });

            Assert.That(topCandidates.GetTopItems().ToList().Count, Is.EqualTo(2));
        }
        public void SizeIsOneLargerThanInputToGetExactSuggestionCount()
        {
            const int    suggestCount    = 2;
            const string tieBreakerInput = "";
            var          topCandidates   = new TopAddressCandidates(suggestCount, new CandidateComparer(tieBreakerInput));

            topCandidates.Add(new Candidate
            {
                Address = "GOLD",
                Score   = 5,
                Weight  = 100
            });

            topCandidates.Add(new Candidate
            {
                Address = "GOLDS",
                Score   = 5,
                Weight  = 100
            });

            topCandidates.Add(new Candidate
            {
                Address = "BRONZE",
                Score   = 5,
                Weight  = 1
            });

            topCandidates.Add(new Candidate
            {
                Address = "SILVER",
                Score   = 5,
                Weight  = 50
            });

            topCandidates.Add(new Candidate
            {
                Address = "Runner up",
                Score   = 5,
                Weight  = 0
            });

            Assert.That(topCandidates.GetTopItems().ToList().Count, Is.EqualTo(suggestCount + 1));
        }
        public void Size_is_one_larger_than_input_to_get_exact_suggestion_count()
        {
            const int    suggestCount    = 2;
            const string tieBreakerInput = "";
            var          topCandidates   = new TopAddressCandidates(suggestCount, new CandidateComparer(tieBreakerInput));

            topCandidates.Add(new Candidate {
                Address = "GOLD",
                Score   = 5,
                Weight  = 100
            });

            topCandidates.Add(new Candidate {
                Address = "GOLDS",
                Score   = 5,
                Weight  = 100
            });

            topCandidates.Add(new Candidate {
                Address = "BRONZE",
                Score   = 5,
                Weight  = 1
            });

            topCandidates.Add(new Candidate {
                Address = "SILVER",
                Score   = 5,
                Weight  = 50
            });

            topCandidates.Add(new Candidate {
                Address = "Runner up",
                Score   = 5,
                Weight  = 0
            });

            topCandidates.Get().ToList().Count.ShouldBe(suggestCount + 1);
        }
        public void Size_is_two_when_suggest_is_zero_for_score_difference_calculating()
        {
            const int    suggestCount    = 0;
            const string tieBreakerInput = "";
            var          topCandidates   = new TopAddressCandidates(suggestCount, new CandidateComparer(tieBreakerInput));

            topCandidates.Add(new Candidate {
                Address = "GOLD",
                Score   = 5,
                Weight  = 100
            });

            topCandidates.Add(new Candidate {
                Address = "GOLDS",
                Score   = 5,
                Weight  = 100
            });

            topCandidates.Add(new Candidate {
                Address = "BRONZE",
                Score   = 5,
                Weight  = 1
            });

            topCandidates.Add(new Candidate {
                Address = "SILVER",
                Score   = 5,
                Weight  = 50
            });

            topCandidates.Add(new Candidate {
                Address = "Runner up",
                Score   = 5,
                Weight  = 0
            });

            topCandidates.Get().ToList().Count.ShouldBe(2);
        }
        public async Task <ObjectResult> Get(string street, string zone, [FromQuery] GeocodingOptions options)
        {
            _log.Debug("Geocoding {street}, {zone} with options: {options}", street, zone, options);

            #region validation

            var errors = "";
            if (string.IsNullOrEmpty(street))
            {
                errors = "Street is empty.";
            }

            if (string.IsNullOrEmpty(zone))
            {
                errors += "Zip code or city name is emtpy";
            }

            if (errors.Length > 0)
            {
                _log.Debug("Bad geocode request", errors);

                return(BadRequest(new ApiResponseContainer <GeocodeAddressApiResponse> {
                    Status = (int)HttpStatusCode.BadRequest,
                    Message = errors
                }));
            }

            street = street?.Trim();
            zone   = zone?.Trim();

            #endregion

            var parseAddressCommand = new AddressParsing.Command(street);
            var parsedStreet        = await _mediator.Send(parseAddressCommand);

            var parseZoneCommand = new ZoneParsing.Command(zone, new GeocodeAddress(parsedStreet));
            var parsedAddress    = await _mediator.Send(parseZoneCommand);

            if (options.PoBox && parsedAddress.IsPoBox && parsedAddress.Zip5.HasValue)
            {
                var poboxCommand = new PoBoxLocation.Command(parsedAddress, options);
                var result       = await _mediator.Send(poboxCommand);

                if (result != null)
                {
                    var model = result.ToResponseObject(street, zone);

                    var standard = parsedAddress.StandardizedAddress.ToLowerInvariant();
                    var input    = street?.ToLowerInvariant();

                    if (input != standard)
                    {
                        model.StandardizedAddress = standard;
                    }

                    _log.Debug("Result score: {score} from {locator}", model.Score, model.Locator);

                    return(Ok(new ApiResponseContainer <GeocodeAddressApiResponse> {
                        Result = model,
                        Status = (int)HttpStatusCode.OK
                    }));
                }
            }

            var deliveryPointCommand = new UspsDeliveryPointLocation.Command(parsedAddress, options);
            var uspsPoint            = await _mediator.Send(deliveryPointCommand);

            if (uspsPoint != null)
            {
                var model = uspsPoint.ToResponseObject(street, zone);

                var standard = parsedAddress.StandardizedAddress.ToLowerInvariant();
                var input    = street?.ToLowerInvariant();

                if (input != standard)
                {
                    model.StandardizedAddress = standard;
                }

                _log.Debug("Result score: {score} from {locator}", model.Score, model.Locator);

                return(Ok(new ApiResponseContainer <GeocodeAddressApiResponse> {
                    Result = model,
                    Status = (int)HttpStatusCode.OK
                }));
            }

            var topCandidates = new TopAddressCandidates(options.Suggest,
                                                         new CandidateComparer(parsedAddress.StandardizedAddress
                                                                               .ToUpperInvariant()));
            var getLocatorsForAddressCommand = new LocatorsForGeocode.Command(parsedAddress, options);
            var locators = await _mediator.Send(getLocatorsForAddressCommand);

            if (locators == null || !locators.Any())
            {
                _log.Debug("No locators found for address {parsedAddress}", parsedAddress);

                return(NotFound(new ApiResponseContainer {
                    Message = $"No address candidates found with a score of {options.AcceptScore} or better.",
                    Status = (int)HttpStatusCode.NotFound
                }));
            }

            var tasks = await Task.WhenAll(locators.Select(locator => _mediator.Send(new Geocode.Command(locator)))
                                           .ToArray());

            var candidates = tasks.SelectMany(x => x);

            foreach (var candidate in candidates)
            {
                topCandidates.Add(candidate);
            }

            var highestScores = topCandidates.Get();

            var chooseBestAddressCandidateCommand = new FilterCandidates.Command(highestScores, options, street,
                                                                                 zone, parsedAddress);
            var winner = await _mediator.Send(chooseBestAddressCandidateCommand);

            if (winner == null || winner.Score < 0)
            {
                _log.Warning("Could not find match for {Street}, {Zone} with a score of {Score} or better.", street,
                             zone,
                             options.AcceptScore);

                return(NotFound(new ApiResponseContainer {
                    Message = $"No address candidates found with a score of {options.AcceptScore} or better.",
                    Status = (int)HttpStatusCode.NotFound
                }));
            }

            if (winner.Location == null)
            {
                _log.Warning("Could not find match for {Street}, {Zone} with a score of {Score} or better.", street,
                             zone,
                             options.AcceptScore);
            }

            winner.Wkid = options.SpatialReference;

            _log.Debug("Result score: {score} from {locator}", winner.Score, winner.Locator);

            return(Ok(new ApiResponseContainer <GeocodeAddressApiResponse> {
                Result = winner,
                Status = (int)HttpStatusCode.OK
            }));
        }
        public async Task <ObjectResult> Get(string street, string zone, [FromQuery] GeocodingOptions options)
        {
            #region validation

            var errors = "";
            if (string.IsNullOrEmpty(street))
            {
                errors = "Street is empty.";
            }

            if (string.IsNullOrEmpty(zone))
            {
                errors += "Zip code or city name is emtpy";
            }

            if (errors.Length > 0)
            {
                return(BadRequest(new ApiResponseContainer <GeocodeAddressApiResponse>
                {
                    Status = (int)HttpStatusCode.BadRequest,
                    Message = errors
                }));
            }

            street = street?.Trim();
            zone   = zone?.Trim();

            #endregion

            _parseAddressCommand.Initialize(street);
            var parsedStreet = CommandExecutor.ExecuteCommand(_parseAddressCommand);

            _parseZoneCommand.Initialize(zone, new GeocodeAddress(parsedStreet));
            var parsedAddress = CommandExecutor.ExecuteCommand(_parseZoneCommand);

            if (options.PoBox && parsedAddress.IsPoBox && parsedAddress.Zip5.HasValue)
            {
                _poboxCommand.Initialize(parsedAddress, options);
                var result = await _poboxCommand.Execute();

                if (result != null)
                {
                    // TODO this is silly change it
                    var model = new GeocodeAddressApiResponse
                    {
                        MatchAddress    = result.Address,
                        Score           = result.Score,
                        Locator         = result.Locator,
                        Location        = result.Location,
                        AddressGrid     = result.AddressGrid,
                        InputAddress    = $"{street}, {zone}",
                        ScoreDifference = result.ScoreDifference
                    };

                    var standard = parsedAddress.StandardizedAddress.ToLowerInvariant();
                    var input    = street?.ToLowerInvariant();

                    if (input != standard)
                    {
                        model.StandardizedAddress = standard;
                    }

                    return(Ok(new ApiResponseContainer <GeocodeAddressApiResponse>
                    {
                        Result = model
                    }));
                }
            }

            _deliveryPointCommand.Initialize(parsedAddress, options);
            var uspsPoint = await _deliveryPointCommand.Execute();

            if (uspsPoint != null)
            {
                // TODO this is silly change it
                var model = new GeocodeAddressApiResponse
                {
                    MatchAddress    = uspsPoint.Address,
                    Score           = uspsPoint.Score,
                    Locator         = uspsPoint.Locator,
                    Location        = uspsPoint.Location,
                    AddressGrid     = uspsPoint.AddressGrid,
                    InputAddress    = $"{street}, {zone}",
                    ScoreDifference = uspsPoint.ScoreDifference
                };

                var standard = parsedAddress.StandardizedAddress.ToLowerInvariant();
                var input    = street?.ToLowerInvariant();

                if (input != standard)
                {
                    model.StandardizedAddress = standard;
                }

                return(Ok(new ApiResponseContainer <GeocodeAddressApiResponse>
                {
                    Result = model
                }));
            }

            var topCandidates = new TopAddressCandidates(options.Suggest,
                                                         new CandidateComparer(parsedAddress.StandardizedAddress
                                                                               .ToUpperInvariant()));
            _getLocatorsForAddressCommand.Initialize(parsedAddress, options);
            var locators = CommandExecutor.ExecuteCommand(_getLocatorsForAddressCommand);

            if (locators == null || !locators.Any())
            {
                return(NotFound(new ApiResponseContainer
                {
                    Message = $"No address candidates found with a score of {options.AcceptScore} or better.",
                    Status = (int)HttpStatusCode.NotFound
                }));
            }

            var commandsToExecute = new ConcurrentQueue <GetAddressCandidatesCommand>();
            foreach (var locator in locators)
            {
                var geocodeWithLocator = new GetAddressCandidatesCommand(_clientFactory);
                geocodeWithLocator.Initialize(locator);

                commandsToExecute.Enqueue(geocodeWithLocator);
            }

            var tasks = new Collection <Task <IEnumerable <Candidate> > >();

            while (commandsToExecute.TryDequeue(out GetAddressCandidatesCommand currentCommand))
            {
                tasks.Add(currentCommand.Execute());
            }

            await Task.WhenAll(tasks);

            var candidates = tasks.Where(x => x.Result != null).SelectMany(x => x.Result);

            foreach (var candidate in candidates)
            {
                topCandidates.Add(candidate);
            }

            var highestScores = topCandidates.Get();

            var chooseBestAddressCandidateCommand = new ChooseBestAddressCandidateCommand(highestScores, options, street,
                                                                                          zone, parsedAddress);
            var winner = CommandExecutor.ExecuteCommand(chooseBestAddressCandidateCommand);

            if (winner == null || winner.Score < 0)
            {
                //                Log.Warning("Could not find match for {Street}, {Zone} with a score of {Score} or better.", street, zone,
                //                            options.AcceptScore);

                return(NotFound(new ApiResponseContainer
                {
                    Message = $"No address candidates found with a score of {options.AcceptScore} or better.",
                    Status = (int)HttpStatusCode.NotFound
                }));
            }

            if (winner.Location == null)
            {
//                Log.Warning("Could not find match for {Street}, {Zone} with a score of {Score} or better.", street, zone,
//                            options.AcceptScore);
            }

            winner.Wkid = options.SpatialReference;

            return(Ok(new ApiResponseContainer <GeocodeAddressApiResponse>
            {
                Result = winner
            }));
        }