private static void SetFilteredBrowsingAttributes(Store store, AttributeFilter[] attributes)
        {
            var browsing = GetFilteredBrowsing(store) ?? new FilteredBrowsing();
            browsing.Attributes = attributes;
            var serializer = new XmlSerializer(typeof(FilteredBrowsing));
            var builder = new StringBuilder();
            var writer = new StringWriter(builder);
            serializer.Serialize(writer, browsing);
            var value = builder.ToString();

            var property = store.DynamicProperties.FirstOrDefault(p => p.Name == _filteredBrowsingPropertyName);

            if (property == null)
            {
                property = new DynamicObjectProperty { Name = _filteredBrowsingPropertyName };
                store.DynamicProperties.Add(property);
            }

            property.Values = new List<DynamicPropertyObjectValue>(new[] { new DynamicPropertyObjectValue { Value = value } });
        }
        private AttributeFilter ConvertToAttributeFilter(Property property)
        {
            var values = _propertyService.SearchDictionaryValues(property.Id, null);

            var result = new AttributeFilter
            {
                Key = property.Name,
                Values = values.Select(ConvertToAttributeFilterValue).ToArray(),
            };

            return result;
        }
        /*
        /// <summary>
        /// Converts the specified filter to filter model.
        /// </summary>
        /// <param name="helper"></param>
        /// <param name="filter">The search filter.</param>
        /// <returns>Filter model</returns>
        public static Facet Convert(this IBrowseFilterService helper, ISearchFilter filter)
        {
            var model = new Facet();

            if (filter is AttributeFilter)
            {
                var prop = filter as AttributeFilter;
                model.Field = prop.Key;
                model.Label = prop.Key;
                model.FacetType = "attr";
                model.Values = prop.Values
                //model.Label = ClientContext.Clients.CreateCatalogClient().GetPropertyName(prop.Key);
                //model.Name = string.IsNullOrEmpty(model.Name) ? model.Key : model.Name;
                return model;
            }
            if (filter is RangeFilter)
            {
                var prop = filter as RangeFilter;
                model.Field = prop.Key;
                model.Label = prop.Key;
                //model.Name = ClientContext.Clients.CreateCatalogClient().GetPropertyName(prop.Key);
                //model.Name = string.IsNullOrEmpty(model.Name) ? model.Key : model.Name;
                return model;
            }
            if (filter is PriceRangeFilter)
            {
                var prop = filter as PriceRangeFilter;
                model.Field = prop.Key;
                model.Label = "Price";
                return model;
            }
            if (filter is CategoryFilter)
            {
                var prop = filter as CategoryFilter;
                model.Field = prop.Key;
                model.Label = "Category";
                return model;
            }

            return null;
        }

        public static FacetValue ToModel(this AttributeFilterValue value)
        {
            var ret = new FacetValue() { Value = value.Value };
            return ret;
        }

        /// <summary>
        /// Converts the specified filter value to facet model.
        /// </summary>
        /// <param name="helper"></param>
        /// <param name="val">The search filter value.</param>
        /// <returns>facet model</returns>
        public static FacetModel Convert(this IBrowseFilterService helper, ISearchFilterValue val)
        {
            var model = new FacetModel();

            if (val is AttributeFilterValue)
            {
                var v = val as AttributeFilterValue;
                model.Key = v.Id;
                model.Name = v.Value;
                return model;
            }
            if (val is CategoryFilterValue)
            {
                var v = val as CategoryFilterValue;
                model.Key = v.Id;
                model.Name = v.Name;
                return model;
            }
            if (val is RangeFilterValue)
            {
                var v = val as RangeFilterValue;
                model.Key = v.Id;

                var name = String.Empty;
                if (v.Displays != null)
                {
                    var disp = (from d in v.Displays where d.Language == "en" select d).SingleOrDefault();
                    if (disp != null)
                    {
                        name = disp.Value;
                    }
                }

                model.Name = name;
                return model;
            }

            return null;
        }

        /// <summary>
        /// Converts the specified facet groups into filter model.
        /// </summary>
        /// <param name="helper">The helper.</param>
        /// <param name="groups">The groups.</param>
        /// <returns>
        /// FilterModel[][].
        /// </returns>
        public static FilterModel[] Convert(this IBrowseFilterService helper, FacetGroup[] groups)
        {
            var list = new List<FilterModel>();
            if (groups != null)
            {
                list.AddRange(groups.Select(x => Convert(helper, x)));
            }

            return list.ToArray();
        }

        /// <summary>
        /// Converts the specified facet group.
        /// </summary>
        /// <param name="helper">The helper.</param>
        /// <param name="group">The facet group.</param>
        /// <returns>
        /// facet group
        /// </returns>
        public static FilterModel Convert(this IBrowseFilterService helper, FacetGroup group)
        {
            return new FilterModel
            {
                Key = @group.FieldName,
                Name = GetDescriptionFromFilter(@group.FieldName),
                Facets = @group.Facets.Select(x => Convert(helper, x)).ToArray()
            };
        }

        /// <summary>
        /// Converts the specified facet to facet model.
        /// </summary>
        /// <param name="helper">The helper.</param>
        /// <param name="facet">The facet.</param>
        /// <returns>
        /// facet model
        /// </returns>
        public static FacetModel Convert(this IBrowseFilterService helper, Facet facet)
        {
            return new FacetModel
            {
                Key = facet.Key,
                Name = GetNameFromFilterValue(helper, facet),
                Count = facet.Count
            };
        }
         * */

        #region Public Methods and Operators

        public static ISearchFilter Convert(this IBrowseFilterService helper, ISearchFilter filter, string[] keys)
        {
            if (filter != null && keys != null)
            {
                // get values that we have filters set for
                var values = from v in filter.GetValues() where keys.Contains(v.Id, StringComparer.OrdinalIgnoreCase) select v;

                var attributeFilter = filter as AttributeFilter;
                if (attributeFilter != null)
                {
                    var newFilter = new AttributeFilter();
                    newFilter.InjectFrom(filter);
                    newFilter.Values = values.OfType<AttributeFilterValue>().ToArray();
                    return newFilter;
                }

                var rangeFilter = filter as RangeFilter;
                if (rangeFilter != null)
                {
                    var newFilter = new RangeFilter();
                    newFilter.InjectFrom(filter);

                    newFilter.Values = values.OfType<RangeFilterValue>().ToArray();
                    return newFilter;
                }

                var priceRangeFilter = filter as PriceRangeFilter;
                if (priceRangeFilter != null)
                {
                    var newFilter = new PriceRangeFilter();
                    newFilter.InjectFrom(filter);

                    newFilter.Values = values.OfType<RangeFilterValue>().ToArray();
                    return newFilter;
                }

                var categoryFilter = filter as CategoryFilter;
                if (categoryFilter != null)
                {
                    var newFilter = new CategoryFilter();
                    newFilter.InjectFrom(filter);
                    newFilter.Values = values.OfType<CategoryFilterValue>().ToArray();
                    return newFilter;
                }
            }

            return null;
        }
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType != typeof(CatalogIndexedSearchCriteria))
            {
                return false;
            }

            var key = actionContext.Request.RequestUri.Query;
            var qs = HttpUtility.ParseQueryString(key);
            var qsDict = this.NvToDict(qs);

            // parse facets
            var facets =
                qsDict.Where(k => FacetRegex.IsMatch(k.Key))
                    .Select(k => k.WithKey(FacetRegex.Replace(k.Key, "")))
                    .ToDictionary(x => x.Key, y => y.Value.Split(','));

            // parse facets
            var terms =
                qsDict.Where(k => TermRegex.IsMatch(k.Key))
                    .Select(k => k.WithKey(TermRegex.Replace(k.Key, "")))
                    .ToDictionary(x => x.Key, y => y.Value.Split(','));

            var result = new CatalogIndexedSearchCriteria
                         {
                             SearchPhrase = qs["q"].EmptyToNull(),
                             RecordsToRetrieve = qs["take"].TryParse(20),
                             StartingRecord = qs["skip"].TryParse(0),
                         };

            // apply filters if one specified
            if (terms.Count > 0)
            {
                foreach (var term in terms)
                {
                    var termFilter = new AttributeFilter
                                     {
                                         Key = term.Key.ToLowerInvariant(),
                                         Values =
                                             term.Value.Select(
                                                 x =>
                                             new AttributeFilterValue()
                                             {
                                                 Id = x.ToLowerInvariant(),
                                                 Value = x.ToLowerInvariant()
                                             }).ToArray()
                                     };

                    result.Apply(termFilter);
                }
            }

            //result.ClassTypes.Add("Product");

            var startDateFromStr = qs["startdatefrom"].EmptyToNull();

            if (!string.IsNullOrWhiteSpace(startDateFromStr))
            {
                DateTime startDateFrom;

                if (DateTime.TryParse(startDateFromStr, out startDateFrom))
                {
                    result.StartDateFrom = startDateFrom;
                }
            }

            //TODO load pricelists
            result.Pricelists = null;
            result.Currency = qs["curreny"].EmptyToNull();

            var sortQuery = qs["sort"].EmptyToNull();
            var sort = string.IsNullOrEmpty(sortQuery) ? "name" : sortQuery;
            var sortOrder = qs["sortorder"].EmptyToNull();

            var outline = qs["outline"].EmptyToNull();

            var isDescending = "desc".Equals(sortOrder, StringComparison.OrdinalIgnoreCase);

            var catalogId = actionContext.ActionArguments.ContainsKey("catalog")
                ? actionContext.ActionArguments["catalog"]
                : null;

            string categoryId = null;

            if (!string.IsNullOrWhiteSpace(outline))
            {
                categoryId = outline.Split(new[] { '/' }).Last();
            }

            SearchSort sortObject = null;

            switch (sort.ToLowerInvariant())
            {
                case "price":
                    if (result.Pricelists != null)
                    {
                        sortObject = new SearchSort(
                            result.Pricelists.Select(
                                priceList =>
                                    new SearchSortField(
                                        String.Format(
                                            "price_{0}_{1}",
                                            result.Currency.ToLower(),
                                            priceList.ToLower()))
                                    {
                                        IgnoredUnmapped = true,
                                        IsDescending = isDescending,
                                        DataType = SearchSortField.DOUBLE
                                    })
                                .ToArray());
                    }
                    break;
                case "position":
                    sortObject =
                        new SearchSort(
                            new SearchSortField(string.Format("sort{0}{1}", catalogId, categoryId).ToLower())
                            {
                                IgnoredUnmapped = true,
                                IsDescending = isDescending
                            });
                    break;
                case "name":
                    sortObject = new SearchSort("name", isDescending);
                    break;
                case "rating":
                    sortObject = new SearchSort(result.ReviewsAverageField, isDescending);
                    break;
                case "reviews":
                    sortObject = new SearchSort(result.ReviewsTotalField, isDescending);
                    break;
                default:
                    sortObject = CatalogIndexedSearchCriteria.DefaultSortOrder;
                    break;
            }

            result.Sort = sortObject;

            //Use fuzzy search to allow spelling error tolerance
            result.IsFuzzySearch = true;

            bindingContext.Model = result;
            return true;
        }
        private AttributeFilter ConvertToAttributeFilter(Property property)
        {
            var values = _propertyService.SearchDictionaryValues(property.Id, null);

            var result = new AttributeFilter
            {
                Key = property.Name,
                Values = values.Select(ConvertToAttributeFilterValue).ToArray(),
                IsLocalized = property.Multilanguage,
                DisplayNames = property.DisplayNames.Select(ConvertToFilterDisplayName).ToArray(),
            };

            return result;
        }
        public void Can_get_item_facets_lucene()
        {
            var scope = "default";
            var queryBuilder = new LuceneSearchQueryBuilder();
            var conn = new SearchConnection(_LuceneStorageDir, scope);
            var provider = new LuceneSearchProvider(queryBuilder, conn);
            Debug.WriteLine("Lucene connection: {0}", conn.ToString());

            if (Directory.Exists(_LuceneStorageDir))
            {
                Directory.Delete(_LuceneStorageDir, true);
            }

            SearchHelper.CreateSampleIndex(provider, scope);

            var criteria = new CatalogIndexedSearchCriteria
            {
                SearchPhrase = "",
                IsFuzzySearch = true,
                Catalog = "goods",
                RecordsToRetrieve = 10,
                StartingRecord = 0,
                Currency = "USD",
                Pricelists = new[] { "default" }
            };

            var filter = new AttributeFilter { Key = "Color" };
            filter.Values = new[]
                                {
                                    new AttributeFilterValue { Id = "red", Value = "red" },
                                    new AttributeFilterValue { Id = "blue", Value = "blue" },
                                    new AttributeFilterValue { Id = "black", Value = "black" }
                                };

            var rangefilter = new RangeFilter { Key = "size" };
            rangefilter.Values = new[]
                                     {
                                         new RangeFilterValue { Id = "0_to_5", Lower = "0", Upper = "5" },
                                         new RangeFilterValue { Id = "5_to_10", Lower = "5", Upper = "10" }
                                     };

            var priceRangefilter = new PriceRangeFilter { Currency = "usd" };
            priceRangefilter.Values = new[]
                                          {
                                              new RangeFilterValue { Id = "0_to_100", Lower = "0", Upper = "100" },
                                              new RangeFilterValue { Id = "100_to_700", Lower = "100", Upper = "700" }
                                          };

            criteria.Add(filter);
            criteria.Add(rangefilter);
            criteria.Add(priceRangefilter);

            var results = provider.Search(scope, criteria);

            Assert.True(results.DocCount == 4, String.Format("Returns {0} instead of 4", results.DocCount));

            var redCount = GetFacetCount(results, "Color", "red");
            Assert.True(redCount == 2, String.Format("Returns {0} facets of red instead of 2", redCount));

            var priceCount = GetFacetCount(results, "Price", "0_to_100");
            Assert.True(priceCount == 2, String.Format("Returns {0} facets of 0_to_100 prices instead of 2", priceCount));

            var priceCount2 = GetFacetCount(results, "Price", "100_to_700");
            Assert.True(priceCount2 == 2, String.Format("Returns {0} facets of 100_to_700 prices instead of 2", priceCount2));

            var sizeCount = GetFacetCount(results, "size", "0_to_5");
            Assert.True(sizeCount == 2, String.Format("Returns {0} facets of 0_to_5 size instead of 2", sizeCount));

            var sizeCount2 = GetFacetCount(results, "size", "5_to_10");
            Assert.True(sizeCount2 == 1, String.Format("Returns {0} facets of 5_to_10 size instead of 1", sizeCount2)); // only 1 result because upper bound is not included

            var outlineCount = results.Documents[0].Documents[0]["__outline"].Values.Count();
            Assert.True(outlineCount == 2, String.Format("Returns {0} outlines instead of 2", outlineCount));

            Directory.Delete(_LuceneStorageDir, true);
        }
        public void Can_get_item_multiple_filters_lucene()
        {
            var scope = "default";
            var queryBuilder = new LuceneSearchQueryBuilder();
            var conn = new SearchConnection(_LuceneStorageDir, scope);
            var provider = new LuceneSearchProvider(queryBuilder, conn);
            Debug.WriteLine("Lucene connection: {0}", conn.ToString());

            if (Directory.Exists(_LuceneStorageDir))
            {
                Directory.Delete(_LuceneStorageDir, true);
            }

            SearchHelper.CreateSampleIndex(provider, scope);

            var criteria = new CatalogIndexedSearchCriteria
            {
                SearchPhrase = "",
                IsFuzzySearch = true,
                Catalog = "goods",
                RecordsToRetrieve = 10,
                StartingRecord = 0,
                Currency = "USD",
                Pricelists = new[] { "default" }
            };

            var colorFilter = new AttributeFilter { Key = "Color" };
            colorFilter.Values = new[]
                                {
                                    new AttributeFilterValue { Id = "red", Value = "red" },
                                    new AttributeFilterValue { Id = "blue", Value = "blue" },
                                    new AttributeFilterValue { Id = "black", Value = "black" }
                                };

            var filter = new AttributeFilter { Key = "Color" };
            filter.Values = new[]
                                {
                                    new AttributeFilterValue { Id = "black", Value = "black" }
                                };

            var rangefilter = new RangeFilter { Key = "size" };
            rangefilter.Values = new[]
                                     {
                                         new RangeFilterValue { Id = "0_to_5", Lower = "0", Upper = "5" },
                                         new RangeFilterValue { Id = "5_to_10", Lower = "5", Upper = "11" }
                                     };

            var priceRangefilter = new PriceRangeFilter { Currency = "usd" };
            priceRangefilter.Values = new[]
                                          {
                                              new RangeFilterValue { Id = "100_to_700", Lower = "100", Upper = "700" }
                                          };

            criteria.Add(colorFilter);
            criteria.Add(priceRangefilter);

            // add applied filters
            criteria.Apply(filter);
            //criteria.Apply(rangefilter);
            //criteria.Apply(priceRangefilter);

            var results = provider.Search(scope, criteria);

            var blackCount = GetFacetCount(results, "Color", "black");
            Assert.True(blackCount == 1, String.Format("Returns {0} facets of black instead of 2", blackCount));

            //Assert.True(results.DocCount == 1, String.Format("Returns {0} instead of 1", results.DocCount));

            Directory.Delete(_LuceneStorageDir, true);
        }