using Lucene.Net.Index;
using Lucene.Net.Util;
using System;
using System.Collections.Generic;

namespace Lucene.Net.Search.Grouping.Terms
{
    /*
     * 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.
     */

    /// <summary>
    /// A collector that collects all groups that match the
    /// query. Only the group value is collected, and the order
    /// is undefined.  This collector does not determine
    /// the most relevant document of a group.
    /// 
    /// <para>
    /// Implementation detail: an int hash set (SentinelIntSet)
    /// is used to detect if a group is already added to the
    /// total count.  For each segment the int set is cleared and filled
    /// with previous counted groups that occur in the new
    /// segment.
    /// </para>
    /// @lucene.experimental
    /// </summary>
    public class TermAllGroupsCollector : AbstractAllGroupsCollector<BytesRef>
    {
        private const int DEFAULT_INITIAL_SIZE = 128;

        private readonly string groupField;
        private readonly SentinelInt32Set ordSet;
        private readonly IList<BytesRef> groups;

        private SortedDocValues index;

        /// <summary>
        /// Expert: Constructs a <see cref="AbstractAllGroupsCollector{BytesRef}"/>
        /// </summary>
        /// <param name="groupField">The field to group by</param>
        /// <param name="initialSize">
        /// The initial allocation size of the
        /// internal int set and group list
        /// which should roughly match the total
        /// number of expected unique groups. Be aware that the
        /// heap usage is 4 bytes * initialSize.
        /// </param>
        public TermAllGroupsCollector(string groupField, int initialSize)
        {
            ordSet = new SentinelInt32Set(initialSize, -2);
            groups = new List<BytesRef>(initialSize);
            this.groupField = groupField;
        }

        /// <summary>
        /// Constructs a <see cref="AbstractAllGroupsCollector{BytesRef}"/>. This sets the
        /// initial allocation size for the internal int set and group
        /// list to 128.
        /// </summary>
        /// <param name="groupField">The field to group by</param>
        public TermAllGroupsCollector(string groupField)
            : this(groupField, DEFAULT_INITIAL_SIZE)
        {
        }

        public override void Collect(int doc)
        {
            int key = index.GetOrd(doc);
            if (!ordSet.Exists(key))
            {
                ordSet.Put(key);
                BytesRef term;
                if (key == -1)
                {
                    term = null;
                }
                else
                {
                    term = new BytesRef();
                    index.LookupOrd(key, term);
                }
                groups.Add(term);
            }
        }

        public override IEnumerable<BytesRef> Groups => groups;

        public override void SetNextReader(AtomicReaderContext context)
        {
            index = FieldCache.DEFAULT.GetTermsIndex(context.AtomicReader, groupField);

            // Clear ordSet and fill it with previous encountered groups that can occur in the current segment.
            ordSet.Clear();
            foreach (BytesRef countedGroup in groups)
            {
                if (countedGroup == null)
                {
                    ordSet.Put(-1);
                }
                else
                {
                    int ord = index.LookupTerm(countedGroup);
                    if (ord >= 0)
                    {
                        ordSet.Put(ord);
                    }
                }
            }
        }
    }
}
