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 Filter = Lucene.Net.Search.Filter;
    using FunctionValues = Lucene.Net.Queries.Function.FunctionValues;
    using IBits = Lucene.Net.Util.IBits;
    using Int64FieldSource = Lucene.Net.Queries.Function.ValueSources.Int64FieldSource;
    using MatchingDocs = FacetsCollector.MatchingDocs;
    using ValueSource = Lucene.Net.Queries.Function.ValueSource;

    /// <summary>
    /// <see cref="Facets"/> implementation that computes counts for
    /// dynamic long ranges from a provided <see cref="ValueSource"/>,
    /// using <see cref="FunctionValues.Int64Val(int)"/> or <see cref="FunctionValues.Int64Val(int, long[])"/>.  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/>
    /// NOTE: This was LongRangeFacetCounts in Lucene
    /// 
    /// @lucene.experimental 
    /// </summary>
    public class Int64RangeFacetCounts : RangeFacetCounts
    {
        /// <summary>
        /// Create <see cref="Int64RangeFacetCounts"/>, using
        /// <see cref="Int64FieldSource"/> from the specified field. 
        /// </summary>
        public Int64RangeFacetCounts(string field, FacetsCollector hits, params Int64Range[] ranges)
            : this(field, new Int64FieldSource(field), hits, ranges)
        {
        }

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

        /// <summary>
        /// Create <see cref="Int64RangeFacetCounts"/>, 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 Int64RangeFacetCounts(string field, ValueSource valueSource, 
            FacetsCollector hits, Filter fastMatchFilter, params Int64Range[] ranges)
            : base(field, ranges, fastMatchFilter)
        {
            Count(valueSource, hits.GetMatchingDocs());
        }

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

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

            Int64RangeCounter counter = new Int64RangeCounter(ranges);

            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(fv.Int64Val(doc));
                    }
                    else
                    {
                        missingCount++;
                    }
                }
            }

            int x = counter.FillCounts(m_counts);

            missingCount += x;

            //System.out.println("totCount " + totCount + " missingCount " + counter.missingCount);
            m_totCount -= missingCount;
        }
    }
}