/// <summary>
        /// Gets the jobs matching the supplied query
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        public Task <JobSearchResult> ReadJobs(JobSearchQuery query)
        {
            var examineQuery = _searcher.CreateSearchCriteria(BooleanOperation.And);

            examineQuery.GroupedOr(new[] { "location" }, query.Locations.ToArray())
            .And().GroupedOr(new[] { "contractType" }, query.ContractTypes.ToArray())
            .And().GroupedOr(new[] { "salaryDisplay" }, query.PayGrades.ToArray())
            .And().GroupedOr(new[] { "workPattern" }, query.WorkPatterns.ToArray())
            .And().GroupedOr(new[] { "jobType" }, query.JobTypes.ToArray())
            .And().GroupedOr(new[] { "department" }, query.Departments.ToArray())
            .And().GroupedOr(new[] { "organisation" }, query.Organisations.ToArray());

            // If any of the GroupedOr values are empty, Examine generates a Lucene query that requires no field set to no value: +()
            // so we need to remove that and use the rest of the generated Lucene query
            var generatedQuery = examineQuery.ToString();

            generatedQuery = generatedQuery.Substring(34, generatedQuery.Length - 36);
            var modifiedQuery = generatedQuery.Replace("+()", String.Empty);

            if (_keywordsQueryBuilder != null)
            {
                // Search for title keywords by building up a clause that looks for any of the keywords to be present
                // in the job title
                modifiedQuery += _keywordsQueryBuilder.AnyOfTheseTermsInThisField(query.KeywordsInTitle ?? String.Empty, new SearchField()
                {
                    FieldName = "title"
                }, true);

                // Search for keywords by building up a clause that looks for all of the keywords to be present
                // in any one of the specified fields.
                modifiedQuery += _keywordsQueryBuilder.AllOfTheseTermsInAnyOfTheseFields(query.Keywords ?? String.Empty, new[]
                {
                    new SearchField()
                    {
                        FieldName = "reference"
                    },
                    new SearchField()
                    {
                        FieldName = "title", Boost = 2
                    },
                    new SearchField()
                    {
                        FieldName = "organisation"
                    },
                    new SearchField()
                    {
                        FieldName = "location"
                    },
                    new SearchField()
                    {
                        FieldName = "jobType"
                    },
                    new SearchField()
                    {
                        FieldName = "contractType"
                    },
                    new SearchField()
                    {
                        FieldName = "department"
                    },
                    new SearchField()
                    {
                        FieldName = "fullText", Boost = 0.5
                    }
                }, true);
            }

            // For the salary ranges we need a structure that Examine's fluent API can't build, so build the raw Lucene query instead
            if (_salaryQueryBuilder != null)
            {
                modifiedQuery += _salaryQueryBuilder.SalaryIsWithinAnyOfTheseRanges(query.SalaryRanges);
            }

            // Append a requirement that the job must not have closed
            if (query.ClosingDateFrom.HasValue)
            {
                modifiedQuery += " +closingDate:[" + query.ClosingDateFrom.Value.ToString("yyyyMMdd000000000") + " TO 30000000000000000]";
            }

            var luceneQuery = _searcher.CreateSearchCriteria(BooleanOperation.And);

            var sortField = String.Empty;

            switch (query.SortBy)
            {
            case JobSearchQuery.JobsSortOrder.JobTitleAscending:
            case JobSearchQuery.JobsSortOrder.JobTitleDescending:
                sortField = "titleDisplay";
                break;

            case JobSearchQuery.JobsSortOrder.LocationAscending:
            case JobSearchQuery.JobsSortOrder.LocationDescending:
                sortField = "locationDisplay";
                break;

            case JobSearchQuery.JobsSortOrder.SalaryRangeAscending:
            case JobSearchQuery.JobsSortOrder.SalaryRangeDescending:
                sortField = "salarySort";
                break;

            case JobSearchQuery.JobsSortOrder.WorkPatternAscending:
            case JobSearchQuery.JobsSortOrder.WorkPatternDescending:
                sortField = "workPattern";
                break;

            case JobSearchQuery.JobsSortOrder.ClosingDateAscending:
            case JobSearchQuery.JobsSortOrder.ClosingDateDescending:
                sortField = "closingDate";
                break;
            }

            if (String.IsNullOrWhiteSpace(modifiedQuery))
            {
                modifiedQuery = "__NodeId:[0 TO 999999]";
            }

            try
            {
                switch (query.SortBy)
                {
                case JobSearchQuery.JobsSortOrder.JobTitleAscending:
                case JobSearchQuery.JobsSortOrder.LocationAscending:
                case JobSearchQuery.JobsSortOrder.SalaryRangeAscending:
                case JobSearchQuery.JobsSortOrder.WorkPatternAscending:
                case JobSearchQuery.JobsSortOrder.ClosingDateAscending:
                    luceneQuery.RawQuery(modifiedQuery).OrderBy(sortField);
                    break;

                case JobSearchQuery.JobsSortOrder.JobTitleDescending:
                case JobSearchQuery.JobsSortOrder.LocationDescending:
                case JobSearchQuery.JobsSortOrder.SalaryRangeDescending:
                case JobSearchQuery.JobsSortOrder.WorkPatternDescending:
                case JobSearchQuery.JobsSortOrder.ClosingDateDescending:
                    luceneQuery.RawQuery(modifiedQuery).OrderByDescending(sortField);
                    break;

                default:
                    luceneQuery.RawQuery(modifiedQuery);
                    break;
                }

                var results = _searcher.Search(luceneQuery);

                var searchResult = new JobSearchResult()
                {
                    TotalJobs = results.Count()
                };

                IEnumerable <SearchResult> selectedResults;
                if (query.PageSize.HasValue)
                {
                    selectedResults = results.Skip((query.CurrentPage - 1) * query.PageSize.Value).Take(query.PageSize.Value);
                }
                else
                {
                    selectedResults = results;
                }

                searchResult.Jobs = BuildJobsFromExamineResults(selectedResults);
                return(Task.FromResult(searchResult));
            }
            catch (ParseException exception)
            {
                exception.Data.Add("Reference", query.JobReference);
                exception.Data.Add("Job types", String.Join(",", query.JobTypes.ToArray()));
                exception.Data.Add("Keywords in title", query.KeywordsInTitle);
                exception.Data.Add("Keywords", query.Keywords);
                exception.Data.Add("Locations", String.Join(",", query.Locations.ToArray()));
                exception.Data.Add("Organisations", String.Join(",", query.Organisations.ToArray()));
                exception.Data.Add("Salary ranges", String.Join(",", query.SalaryRanges.ToArray()));
                exception.Data.Add("Contract types", String.Join(",", query.ContractTypes.ToArray()));
                exception.Data.Add("Work patterns", String.Join(",", query.WorkPatterns.ToArray()));
                exception.Data.Add("Sort", query.SortBy);
                exception.Data.Add("Generated query", modifiedQuery);
                exception.ToExceptionless().Submit();

                var errorForLog = new StringBuilder($"Lucene.net could not parse {modifiedQuery} generated from parameters:").Append(Environment.NewLine)
                                  .Append("Reference:").Append(query.JobReference).Append(Environment.NewLine)
                                  .Append("Job types:").Append(String.Join(",", query.JobTypes.ToArray())).Append(Environment.NewLine)
                                  .Append("Keywords in title:").Append(query.KeywordsInTitle).Append(Environment.NewLine)
                                  .Append("Keywords:").Append(query.Keywords).Append(Environment.NewLine)
                                  .Append("Locations:").Append(String.Join(",", query.Locations.ToArray())).Append(Environment.NewLine)
                                  .Append("Organisations:").Append(String.Join(",", query.Organisations.ToArray())).Append(Environment.NewLine)
                                  .Append("Salary ranges:").Append(String.Join(",", query.SalaryRanges.ToArray())).Append(Environment.NewLine)
                                  .Append("Contract types:").Append(String.Join(",", query.ContractTypes.ToArray())).Append(Environment.NewLine)
                                  .Append("Work patterns:").Append(String.Join(",", query.WorkPatterns.ToArray())).Append(Environment.NewLine)
                                  .Append("Sort:").Append(query.SortBy).Append(Environment.NewLine);
                _log.Error(errorForLog.ToString());

                return(Task.FromResult(new JobSearchResult()));
            }
        }