/// <summary>
        /// Creates result document collection from Lucene documents.
        /// </summary>
        /// <param name="searcher">The searcher.</param>
        /// <param name="topDocs">The hits.</param>
        private void CreateDocuments(Searcher searcher, TopDocs topDocs)
        {
            // if no documents found return
            if (topDocs == null)
                return;

            var entries = new List<ResultDocument>();

            // get total hits
            var totalCount = topDocs.TotalHits;
            var recordsToRetrieve = Results.SearchCriteria.RecordsToRetrieve;
            var startIndex = Results.SearchCriteria.StartingRecord;
            if (recordsToRetrieve > totalCount)
                recordsToRetrieve = totalCount;

            for (var index = startIndex; index < startIndex + recordsToRetrieve; index++)
            {
                if (index >= totalCount)
                    break;

                var document = searcher.Doc(topDocs.ScoreDocs[index].Doc);
                var doc = new ResultDocument();

                var documentFields = document.GetFields();
                using (var fi = documentFields.GetEnumerator())
                {
                    while (fi.MoveNext())
                    {
                        if (fi.Current != null)
                        {
                            var field = fi.Current;
                            doc.Add(new DocumentField(field.Name, field.StringValue));
                        }
                    }
                }

                entries.Add(doc);
            }

            var searchDocuments = new ResultDocumentSet
            {
                Name = "Items",
                Documents = entries.OfType<IDocument>().ToArray(),
                TotalCount = totalCount
            };

            Results.Documents = new[] { searchDocuments };
        }
        public IEnumerable<IDocument> CreateDocuments(Partition partition)
        {
            if (partition == null)
                throw new ArgumentNullException("partition");

            var documents = new ConcurrentBag<IDocument>();

            var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 5 };

            Parallel.ForEach(partition.Keys, parallelOptions, key =>
            {
                //Trace.TraceInformation(string.Format("Processing documents starting {0} of {1} - {2}%", partition.Start, partition.Total, (partition.Start * 100 / partition.Total)));
                if (key != null)
                {
                    var doc = new ResultDocument();
                    IndexItem(ref doc, key);
                    documents.Add(doc);
                }
            });

            return documents;
        }
        protected virtual void IndexItemPrices(ref ResultDocument doc, CatalogProduct item)
        {
            var evalContext = new Domain.Pricing.Model.PriceEvaluationContext
            {
                ProductIds = new[] { item.Id }
            };

            var prices = _pricingService.EvaluateProductPrices(evalContext);


            foreach (var price in prices)
            {
                //var priceList = price.Pricelist;
                doc.Add(new DocumentField(string.Format("price_{0}_{1}", price.Currency, price.PricelistId), price.EffectiveValue, new[] { IndexStore.No, IndexType.NotAnalyzed }));
                doc.Add(new DocumentField(string.Format("price_{0}_{1}_value", price.Currency, price.PricelistId), (price.EffectiveValue).ToString(CultureInfo.InvariantCulture), new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            }

        }
        protected virtual void IndexItemCustomProperties(ref ResultDocument doc, CatalogProduct item)
        {
            var properties = item.CategoryId != null ? _propertyService.GetCategoryProperties(item.CategoryId) : _propertyService.GetCatalogProperties(item.CatalogId);

            foreach (var propValue in item.PropertyValues.Where(x => x.Value != null))
            {
                var property = properties.FirstOrDefault(x => string.Equals(x.Name, propValue.PropertyName, StringComparison.InvariantCultureIgnoreCase) && x.ValueType == propValue.ValueType);
                var contentField = string.Format("__content{0}", property != null && (property.Multilanguage && !string.IsNullOrWhiteSpace(propValue.LanguageCode)) ? "_" + propValue.LanguageCode.ToLower() : string.Empty);

                switch (propValue.ValueType)
                {
                    case PropertyValueType.LongText:
                    case PropertyValueType.ShortText:
                        var stringValue = propValue.Value.ToString();

                        if (!string.IsNullOrWhiteSpace(stringValue)) // don't index empty values
                        {
                            doc.Add(new DocumentField(contentField, stringValue.ToLower(), new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection }));
                        }

                        break;
                }

                if (doc.ContainsKey(propValue.PropertyName))
                    continue;


                switch (propValue.ValueType)
                {
                    case PropertyValueType.Boolean:
                    case PropertyValueType.DateTime:
                    case PropertyValueType.Number:
                        doc.Add(new DocumentField(propValue.PropertyName, propValue.Value, new[] { IndexStore.Yes, IndexType.Analyzed }));
                        break;
                    case PropertyValueType.LongText:
                    case PropertyValueType.ShortText:
                        doc.Add(new DocumentField(propValue.PropertyName, propValue.Value.ToString().ToLower(), new[] { IndexStore.Yes, IndexType.Analyzed }));
                        break;
                }
            }
        }
        protected virtual void IndexItem(ref ResultDocument doc, string productId)
        {
            var item = _itemService.GetById(productId, ItemResponseGroup.ItemProperties | ItemResponseGroup.Categories);

            doc.Add(new DocumentField("__key", item.Id.ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            //doc.Add(new DocumentField("__loc", "en-us", new[] { IndexStore.YES, IndexType.NOT_ANALYZED }));
            doc.Add(new DocumentField("__type", item.GetType().Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("__sort", item.Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("__hidden", (!item.IsActive.Value || item.MainProductId != null).ToString().ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("code", item.Code, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("name", item.Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("startdate", item.StartDate, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("enddate", item.EndDate.HasValue ? item.EndDate : DateTime.MaxValue, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("createddate", item.CreatedDate, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("lastmodifieddate", item.ModifiedDate ?? DateTime.MaxValue, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("catalog", item.CatalogId.ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection }));
            doc.Add(new DocumentField("__outline", item.CatalogId.ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection }));

			
			var outlines = new List<string>();
			if (item.CategoryId != null)
			{
				outlines.AddRange(_catalogOutlineBuilder.GetOutlines(item.CategoryId));
			}
            //Index item direct categories links
            if (item.Links != null)
            {
                foreach (var link in item.Links)
                {
                    if (link.CategoryId != null)
                    {
                        outlines.AddRange(_catalogOutlineBuilder.GetOutlines(link.CategoryId));
                        //doc.Add(new DocumentField(string.Format("sort{0}{1}", link.CatalogId, link.CategoryId), category.Priority, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
                    }
                }
                //Add outlines to search index
                foreach (var outline in outlines.Distinct())
                {
                    doc.Add(new DocumentField("__outline", outline.ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
                }
                //Add all linked catalogs to search index
                foreach (var catalogId in outlines.Select(x => x.Split('/').FirstOrDefault()).Where(x => x != null).Distinct())
                {
                    doc.Add(new DocumentField("catalog", catalogId.ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
                }
            }

            // Index custom properties
            IndexItemCustomProperties(ref doc, item);

            // Index item prices
            IndexItemPrices(ref doc, item);

            //Index item reviews
            //IndexReviews(ref doc, item);

            // add to content
            doc.Add(new DocumentField("__content", item.Name, new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection }));
            doc.Add(new DocumentField("__content", item.Code, new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection }));
        }
        public ISearchResults Search(string scope, ISearchCriteria criteria)
        {
            // Build query
            var builder = (SearchQuery)_queryBuilder.BuildQuery(criteria);

            SearchQueryResult resultDocs;

            // Add some error handling
            //try
            {
                var searchResponse = Client.Search(scope, builder).Result;
                if (!searchResponse.IsSuccess)
                {
                    throw new AzureSearchException(AzureSearchHelper.FormatSearchException(searchResponse));
                }

                resultDocs = searchResponse.Body;
            }
            /*
        catch (Exception ex)
        {
            throw ex;
        }
             * */

            // Parse documents returned
            var documents = new ResultDocumentSet { TotalCount = resultDocs.Count };
            var docList = new List<ResultDocument>();
            foreach (var indexDoc in resultDocs.Records)
            {
                var document = new ResultDocument();
                foreach (var field in indexDoc.Properties.Keys)
                {
                    document.Add(new DocumentField(field, indexDoc.Properties[field]));
                }

                docList.Add(document);
            }

            documents.Documents = docList.ToArray();

            // Create search results object
            var results = new SearchResults(criteria, new[] { documents });

            return results;
        }
        public virtual ISearchResults Search(string scope, ISearchCriteria criteria)
        {
            var command = new SearchCommand(scope, criteria.DocumentType);

            command.Size(criteria.RecordsToRetrieve);
            command.From(criteria.StartingRecord);

            // Add spell checking
            // TODO: options.SpellCheck = new SpellCheckingParameters { Collate = true };

            // Build query
            var builder = (QueryBuilder<ESDocument>)_queryBuilder.BuildQuery(criteria);

            SearchResult<ESDocument> resultDocs;

            // Add some error handling
            try
            {
                resultDocs = Client.Search(command, builder);
            }
            catch (Exception ex)
            {
                throw new ElasticSearchException("Search using Elastic Search server failed, check logs for more details.", ex);
            }

            // Parse documents returned
            var documents = new ResultDocumentSet { TotalCount = resultDocs.hits.total };
            var docList = new List<ResultDocument>();
            foreach (var indexDoc in resultDocs.Documents)
            {
                var document = new ResultDocument();
                foreach (var field in indexDoc.Keys)
                    document.Add(new DocumentField(field, indexDoc[field]));

                docList.Add(document);
            }

            documents.Documents = docList.ToArray();

            // Create search results object
            var results = new SearchResults(criteria, new[] { documents });

            // Now add facet results
            var groups = new List<FacetGroup>();

            if (resultDocs.facets != null)
            {
                foreach (var filter in criteria.Filters)
                {
                    var groupCount = 0;

                    var group = new FacetGroup(filter.Key);

                    if (filter is AttributeFilter)
                    {
                        group.FacetType = FacetTypes.Attribute;
                        var attributeFilter = filter as AttributeFilter;
                        var myFilter = attributeFilter;
                        var values = myFilter.Values;
                        if (values != null)
                        {
                            var key = filter.Key.ToLower();
                            if (!resultDocs.facets.ContainsKey(key))
                                continue;

                            var facet = resultDocs.facets[key] as TermsFacetResult;
                            if (facet != null)
                            {
                                foreach (var value in values)
                                {
                                    //facet.terms
                                    var termCount = from f in facet.terms where f.term.Equals(value.Id, StringComparison.OrdinalIgnoreCase) select f.count;

                                    var enumerable = termCount as int[] ?? termCount.ToArray();
                                    if (!enumerable.Any())
                                        continue;

                                    //var facet = from resultFacet
                                    var newFacet = new Facet(@group, value.Id, GetDescription(value, criteria.Locale), enumerable.SingleOrDefault());
                                    @group.Facets.Add(newFacet);
                                }

                                groupCount++;
                            }
                        }
                    }
                    else if (filter is PriceRangeFilter)
                    {
                        group.FacetType = FacetTypes.PriceRange;
                        var rangeFilter = filter as PriceRangeFilter;
                        if (rangeFilter != null
                            && rangeFilter.Currency.Equals(criteria.Currency, StringComparison.OrdinalIgnoreCase))
                        {
                            var myFilter = rangeFilter;
                            var values = myFilter.Values;
                            if (values != null)
                            {
                                values = rangeFilter.Values;

                                foreach (var value in values)
                                {
                                    var key = String.Format("{0}-{1}", myFilter.Key, value.Id).ToLower();

                                    if (!resultDocs.facets.ContainsKey(key))
                                        continue;

                                    var facet = resultDocs.facets[key] as FilterFacetResult;

                                    if (facet != null && facet.count > 0)
                                    {
                                        if (facet.count == 0)
                                            continue;

                                        var myFacet = new Facet(
                                            @group, value.Id, GetDescription(value, criteria.Locale), facet.count);
                                        @group.Facets.Add(myFacet);

                                        groupCount++;
                                    }
                                }
                            }
                        }
                    }
                    else if (filter is RangeFilter)
                    {
                        group.FacetType = FacetTypes.Range;
                        var myFilter = filter as RangeFilter;
                        if (myFilter != null)
                        {
                            var values = myFilter.Values;
                            if (values != null)
                            {
                                foreach (var value in values)
                                {
                                    var facet = resultDocs.facets[filter.Key] as FilterFacetResult;

                                    if (facet == null || facet.count <= 0)
                                    {
                                        continue;
                                    }

                                    var myFacet = new Facet(
                                        @group, value.Id, GetDescription(value, criteria.Locale), facet.count);
                                    @group.Facets.Add(myFacet);

                                    groupCount++;
                                }
                            }
                        }
                    }
                    else if (filter is CategoryFilter)
                    {
                        group.FacetType = FacetTypes.Category;
                        var myFilter = filter as CategoryFilter;
                        if (myFilter != null)
                        {
                            var values = myFilter.Values;
                            if (values != null)
                            {
                                foreach (var value in values)
                                {
                                    var key = String.Format("{0}-{1}", myFilter.Key.ToLower(), value.Id.ToLower()).ToLower();
                                    var facet = resultDocs.facets[key] as FilterFacetResult;

                                    if (facet == null || facet.count <= 0)
                                    {
                                        continue;
                                    }

                                    var myFacet = new Facet(
                                        @group, value.Id, GetDescription(value, criteria.Locale), facet.count);
                                    @group.Facets.Add(myFacet);

                                    groupCount++;
                                }
                            }
                        }
                    }

                    // Add only if items exist under
                    if (groupCount > 0)
                    {
                        groups.Add(group);
                    }
                }
            }

            results.FacetGroups = groups.ToArray();
            return results;
        }
        /// <summary>
        /// Creates result document collection from Lucene documents.
        /// </summary>
        /// <param name="searcher">The searcher.</param>
        /// <param name="topDocs">The hits.</param>
        private void CreateDocuments(Searcher searcher, TopDocs topDocs)
        {
            // if no documents found return
            if (topDocs == null)
                return;

            var entries = new List<ResultDocument>();

            // get total hits
            var totalCount = topDocs.TotalHits;
            var recordsToRetrieve = Results.SearchCriteria.RecordsToRetrieve;
            var startIndex = Results.SearchCriteria.StartingRecord;
            if (recordsToRetrieve > totalCount)
                recordsToRetrieve = totalCount;

            for (var index = startIndex; index < startIndex + recordsToRetrieve; index++)
            {
                if (index >= totalCount)
                    break;

                var document = searcher.Doc(topDocs.ScoreDocs[index].Doc);
                var doc = new ResultDocument();

                var documentFields = document.GetFields();
                using (var fi = documentFields.GetEnumerator())
                {
                    while (fi.MoveNext())
                    {
                        if (fi.Current != null)
                        {
                            var field = fi.Current;

                            // make sure document field doens't exist, if it does, simply add another value
                            if (doc.ContainsKey(field.Name))
                            {
                                var existingField = doc[field.Name] as DocumentField;
                                if (existingField != null)
                                    existingField.AddValue(field.StringValue);
                            }
                            else // add new
                            {
                                doc.Add(new DocumentField(field.Name, field.StringValue));
                            }
                        }
                    }
                }

                entries.Add(doc);
            }

            var searchDocuments = new ResultDocumentSet
            {
                Name = "Items",
                Documents = entries.ToArray(),
                TotalCount = totalCount
            };

            Results.Documents = new[] { searchDocuments };
        }
        protected virtual void IndexItemCustomProperties(ref ResultDocument doc, CatalogProduct item)
        {
            var properties = item.Properties;

            foreach (var propValue in item.PropertyValues.Where(x => x.Value != null))
            {
                var propertyName = propValue.PropertyName.ToLower();
                var property = properties.FirstOrDefault(x => string.Equals(x.Name, propValue.PropertyName, StringComparison.InvariantCultureIgnoreCase) && x.ValueType == propValue.ValueType);
                var contentField = string.Concat("__content", property != null && property.Multilanguage && !string.IsNullOrWhiteSpace(propValue.LanguageCode) ? "_" + propValue.LanguageCode.ToLower() : string.Empty);

                switch (propValue.ValueType)
                {
                    case PropertyValueType.LongText:
                    case PropertyValueType.ShortText:
                        var stringValue = propValue.Value.ToString();

                        if (!string.IsNullOrWhiteSpace(stringValue)) // don't index empty values
                        {
                            doc.Add(new DocumentField(contentField, stringValue.ToLower(), new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection }));
                        }

                        break;
                }

                switch (propValue.ValueType)
                {
                    case PropertyValueType.Boolean:
                    case PropertyValueType.DateTime:
                    case PropertyValueType.Number:
                        doc.Add(new DocumentField(propertyName, propValue.Value, new[] { IndexStore.Yes, IndexType.Analyzed }));
                        break;
                    case PropertyValueType.LongText:
                        doc.Add(new DocumentField(propertyName, propValue.Value.ToString().ToLowerInvariant(), new[] { IndexStore.Yes, IndexType.Analyzed }));
                        break;
                    case PropertyValueType.ShortText: // do not tokenize small values as they will be used for lookups and filters
                        doc.Add(new DocumentField(propertyName, propValue.Value.ToString(), new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
                        break;
                }
            }
        }
        protected virtual void IndexItem(ref ResultDocument doc, string productId)
        {
            var item = _itemService.GetById(productId, ItemResponseGroup.ItemProperties | ItemResponseGroup.Links | ItemResponseGroup.Variations);
            if (item == null)
                return;

            doc.Add(new DocumentField("__key", item.Id.ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            //doc.Add(new DocumentField("__loc", "en-us", new[] { IndexStore.YES, IndexType.NOT_ANALYZED }));
            doc.Add(new DocumentField("__type", item.GetType().Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("__sort", item.Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("__hidden", (item.IsActive != true || item.MainProductId != null).ToString().ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("code", item.Code, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("name", item.Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("startdate", item.StartDate, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("enddate", item.EndDate.HasValue ? item.EndDate : DateTime.MaxValue, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("createddate", item.CreatedDate, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("lastmodifieddate", item.ModifiedDate ?? DateTime.MaxValue, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));

            var catalogs = new List<string> { item.CatalogId };
            var outlines = new List<string> { item.CatalogId };

            if (item.CategoryId != null)
            {
                outlines.AddRange(_catalogOutlineBuilder.GetOutlines(item.CategoryId));
            }

            // Index item direct categories links
            if (item.Links != null)
            {
                foreach (var link in item.Links.Where(link => link.CategoryId != null))
                {
                    outlines.AddRange(_catalogOutlineBuilder.GetOutlines(link.CategoryId));
                }

                // Add all linked catalogs to search index
                catalogs.AddRange(outlines.Select(x => x.Split('/').FirstOrDefault()).Where(x => x != null).Distinct());
            }

            var indexStoreNotAnalyzedStringCollection = new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection };

            // Add catalogs to search index
            foreach (var catalogId in catalogs.Distinct(StringComparer.OrdinalIgnoreCase))
            {
                doc.Add(new DocumentField("catalog", catalogId.ToLower(), indexStoreNotAnalyzedStringCollection));
            }

            // Add outlines to search index
            foreach (var outline in outlines.Distinct(StringComparer.OrdinalIgnoreCase))
            {
                doc.Add(new DocumentField("__outline", outline.ToLower(), indexStoreNotAnalyzedStringCollection));
            }

            // Index custom properties
            IndexItemCustomProperties(ref doc, item);

            if (item.Variations != null)
            {
                if (item.Variations.Any(c => c.ProductType == "Physical"))
                {
                    doc.Add(new DocumentField("producttype", "Physical", new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection }));
                }

                if (item.Variations.Any(c => c.ProductType == "Digital"))
                {
                    doc.Add(new DocumentField("producttype", "Digital", new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection }));
                }

                foreach (var variation in item.Variations)
                {
                    IndexItemCustomProperties(ref doc, variation);
                }
            }

            // Index item prices
            IndexItemPrices(ref doc, item);

            //Index item reviews
            //IndexReviews(ref doc, item);

            // add to content
            doc.Add(new DocumentField("__content", item.Name, new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection }));
            doc.Add(new DocumentField("__content", item.Code, new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection }));
        }
        protected virtual void IndexItem(ref ResultDocument doc, string productId)
        {
            var item = _itemService.GetById(productId, ItemResponseGroup.ItemProperties | ItemResponseGroup.Variations | ItemResponseGroup.Outlines);
            if (item == null)
                return;

            doc.Add(new DocumentField("__key", item.Id.ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            //doc.Add(new DocumentField("__loc", "en-us", new[] { IndexStore.YES, IndexType.NOT_ANALYZED }));
            doc.Add(new DocumentField("__type", item.GetType().Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("__sort", item.Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("__hidden", (item.IsActive != true || item.MainProductId != null).ToString().ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("code", item.Code, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("name", item.Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("startdate", item.StartDate, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("enddate", item.EndDate.HasValue ? item.EndDate : DateTime.MaxValue, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("createddate", item.CreatedDate, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));
            doc.Add(new DocumentField("lastmodifieddate", item.ModifiedDate ?? DateTime.MaxValue, new[] { IndexStore.Yes, IndexType.NotAnalyzed }));

            var indexStoreNotAnalyzedStringCollection = new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection };

            // Add catalogs to search index
            var catalogs = item.Outlines
                .Select(o => o.Items.First().Id)
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .ToArray();

            foreach (var catalogId in catalogs)
            {
                doc.Add(new DocumentField("catalog", catalogId.ToLower(), indexStoreNotAnalyzedStringCollection));
            }

            // Add outlines to search index
            var outlineStrings = GetOutlineStrings(item.Outlines);
            foreach (var outline in outlineStrings)
            {
                doc.Add(new DocumentField("__outline", outline.ToLower(), indexStoreNotAnalyzedStringCollection));
            }

            // Index custom properties
            IndexItemCustomProperties(ref doc, item);

            if (item.Variations != null)
            {
                if (item.Variations.Any(c => c.ProductType == "Physical"))
                {
                    doc.Add(new DocumentField("producttype", "Physical", new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection }));
                }

                if (item.Variations.Any(c => c.ProductType == "Digital"))
                {
                    doc.Add(new DocumentField("producttype", "Digital", new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection }));
                }

                foreach (var variation in item.Variations)
                {
                    IndexItemCustomProperties(ref doc, variation);
                }
            }

            // Index item prices
            IndexItemPrices(ref doc, item);

            //Index item reviews
            //IndexReviews(ref doc, item);

            // add to content
            doc.Add(new DocumentField("__content", item.Name, new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection }));
            doc.Add(new DocumentField("__content", item.Code, new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection }));
        }
        public virtual ISearchResults Search(string scope, ISearchCriteria criteria)
        {
            var command = new SearchCommand(scope, criteria.DocumentType);

            command.Size(criteria.RecordsToRetrieve);
            command.From(criteria.StartingRecord);

            // Add spell checking
            // TODO: options.SpellCheck = new SpellCheckingParameters { Collate = true };

            // Build query
            var builder = (QueryBuilder<ESDocument>)_queryBuilder.BuildQuery(criteria);

            SearchResult<ESDocument> resultDocs;

            // Add some error handling
            try
            {
                resultDocs = Client.Search(command, builder);
            }
            catch (Exception ex)
            {
                throw new ElasticSearchException("Search using Elastic Search server failed, check logs for more details.", ex);
            }

            // Parse documents returned
            var docList = new List<ResultDocument>();

            foreach (var indexDoc in resultDocs.Documents)
            {
                var document = new ResultDocument();
                foreach (var field in indexDoc.Keys)
                    document.Add(new DocumentField(field, indexDoc[field]));

                docList.Add(document);
            }

            var documents = new ResultDocumentSet
            {
                TotalCount = resultDocs.hits.total,
                Documents = docList.OfType<IDocument>().ToArray()
            };

            // Create search results object
            var results = new SearchResults(criteria, new[] { documents })
            {
                FacetGroups = CreateFacets(criteria, resultDocs.facets)
            };

            return results;
        }