using Lucene.Net.Support;
using System;
using System.Collections.Generic;

namespace Lucene.Net.Facet.Range
{
    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    using DocIdSet = Lucene.Net.Search.DocIdSet;
    using DocIdSetIterator = Lucene.Net.Search.DocIdSetIterator;
    using DoubleFieldSource = Lucene.Net.Queries.Function.ValueSources.DoubleFieldSource;
    using Filter = Lucene.Net.Search.Filter;
    using FunctionValues = Lucene.Net.Queries.Function.FunctionValues;
    using IBits = Lucene.Net.Util.IBits;
    using MatchingDocs = FacetsCollector.MatchingDocs;
    using NumericUtils = Lucene.Net.Util.NumericUtils;
    using ValueSource = Lucene.Net.Queries.Function.ValueSource;

    /// <summary>
    /// <see cref="Facets"/> implementation that computes counts for
    ///  dynamic double ranges from a provided <see cref="ValueSource"/>, 
    ///  using <see cref="FunctionValues.DoubleVal(int)"/> or <see cref="FunctionValues.DoubleVal(int, double[])"/>.  Use
    ///  this for dimensions that change in real-time (e.g. a
    ///  relative time based dimension like "Past day", "Past 2
    ///  days", etc.) or that change for each request (e.g.
    ///  distance from the user's location, "&lt; 1 km", "&lt; 2 km",
    ///  etc.).
    /// 
    ///  <para> If you had indexed your field using <see cref="Documents.SingleDocValuesField"/> 
    ///  then pass <see cref="Queries.Function.ValueSources.SingleFieldSource"/>
    ///  as the <see cref="ValueSource"/>; if you used 
    ///  <see cref="Documents.DoubleDocValuesField"/> then pass 
    ///  <see cref="DoubleFieldSource"/> (this is the default used when you
    ///  pass just a the field name).
    /// 
    /// @lucene.experimental 
    /// </para>
    /// </summary>
    public class DoubleRangeFacetCounts : RangeFacetCounts
    {
        /// <summary>
        /// Create <see cref="RangeFacetCounts"/>, using 
        /// <see cref="DoubleFieldSource"/> from the specified field. 
        /// </summary>
        public DoubleRangeFacetCounts(string field, FacetsCollector hits, params DoubleRange[] ranges)
            : this(field, new DoubleFieldSource(field), hits, ranges)
        {
        }

        /// <summary>
        /// Create <see cref="RangeFacetCounts"/>, using the provided
        /// <see cref="ValueSource"/>. 
        /// </summary>
        public DoubleRangeFacetCounts(string field, ValueSource valueSource, FacetsCollector hits, params DoubleRange[] ranges)
            : this(field, valueSource, hits, null, ranges)
        {
        }

        /// <summary>
        /// Create <see cref="RangeFacetCounts"/>, using the provided
        /// <see cref="ValueSource"/>, and using the provided Filter as
        /// a fastmatch: only documents passing the filter are
        /// checked for the matching ranges.  The filter must be
        /// random access (implement <see cref="DocIdSet.Bits"/>). 
        /// </summary>
        public DoubleRangeFacetCounts(string field, ValueSource valueSource, FacetsCollector hits, Filter fastMatchFilter, params DoubleRange[] ranges)
            : base(field, ranges, fastMatchFilter)
        {
            Count(valueSource, hits.GetMatchingDocs());
        }

        private void Count(ValueSource valueSource, IEnumerable<MatchingDocs> matchingDocs)
        {

            DoubleRange[] ranges = (DoubleRange[])this.m_ranges;

            Int64Range[] longRanges = new Int64Range[ranges.Length];
            for (int i = 0; i < ranges.Length; i++)
            {
                DoubleRange range = ranges[i];
                longRanges[i] = new Int64Range(range.Label, NumericUtils.DoubleToSortableInt64(range.minIncl), true, NumericUtils.DoubleToSortableInt64(range.maxIncl), true);
            }

            Int64RangeCounter counter = new Int64RangeCounter(longRanges);

            int missingCount = 0;
            foreach (MatchingDocs hits in matchingDocs)
            {
                FunctionValues fv = valueSource.GetValues(Collections.EmptyMap<string, object>(), hits.Context);

                m_totCount += hits.TotalHits;
                IBits bits;
                if (m_fastMatchFilter != null)
                {
                    DocIdSet dis = m_fastMatchFilter.GetDocIdSet(hits.Context, null);
                    if (dis == null)
                    {
                        // No documents match
                        continue;
                    }
                    bits = dis.Bits;
                    if (bits == null)
                    {
                        throw new ArgumentException("fastMatchFilter does not implement DocIdSet.bits");
                    }
                }
                else
                {
                    bits = null;
                }

                DocIdSetIterator docs = hits.Bits.GetIterator();

                int doc;
                while ((doc = docs.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
                {
                    if (bits != null && bits.Get(doc) == false)
                    {
                        doc++;
                        continue;
                    }
                    // Skip missing docs:
                    if (fv.Exists(doc))
                    {
                        counter.Add(NumericUtils.DoubleToSortableInt64(fv.DoubleVal(doc)));
                    }
                    else
                    {
                        missingCount++;
                    }
                }
            }

            missingCount += counter.FillCounts(m_counts);
            m_totCount -= missingCount;
        }
    }
}