using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Util;
using System;

namespace Lucene.Net.Join
{
    /*
     * 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 field comparer that allows parent documents to be sorted by fields
    /// from the nested / child documents.
    /// 
    /// @lucene.experimental
    /// </summary>
    public abstract class ToParentBlockJoinFieldComparer : FieldComparer<object>
    {
        private readonly Filter _parentFilter;
        private readonly Filter _childFilter;
        private readonly int _spareSlot;

        private FieldComparer _wrappedComparer;
        private FixedBitSet _parentDocuments;
        private FixedBitSet _childDocuments;

        private ToParentBlockJoinFieldComparer(FieldComparer wrappedComparer, Filter parentFilter, Filter childFilter, int spareSlot)
        {
            _wrappedComparer = wrappedComparer;
            _parentFilter = parentFilter;
            _childFilter = childFilter;
            _spareSlot = spareSlot;
        }

        public override int Compare(int slot1, int slot2)
        {
            return _wrappedComparer.Compare(slot1, slot2);
        }

        public override void SetBottom(int slot)
        {
            _wrappedComparer.SetBottom(slot);
        }

        public override void SetTopValue(object value)
        {
            _wrappedComparer.SetTopValue(value);
        }
        
        public override FieldComparer SetNextReader(AtomicReaderContext context)
        {
            DocIdSet innerDocuments = _childFilter.GetDocIdSet(context, null);
            if (IsEmpty(innerDocuments))
            {
                _childDocuments = null;
            }
            else if (innerDocuments is FixedBitSet)
            {
                _childDocuments = (FixedBitSet)innerDocuments;
            }
            else
            {
                DocIdSetIterator iterator = innerDocuments.GetIterator();
                _childDocuments = iterator != null ? ToFixedBitSet(iterator, context.AtomicReader.MaxDoc) : null;
            }
            DocIdSet rootDocuments = _parentFilter.GetDocIdSet(context, null);
            if (IsEmpty(rootDocuments))
            {
                _parentDocuments = null;
            }
            else if (rootDocuments is FixedBitSet)
            {
                _parentDocuments = (FixedBitSet)rootDocuments;
            }
            else
            {
                DocIdSetIterator iterator = rootDocuments.GetIterator();
                _parentDocuments = iterator != null ? ToFixedBitSet(iterator, context.AtomicReader.MaxDoc) : null;
            }

            _wrappedComparer = _wrappedComparer.SetNextReader(context);
            return this;
        }

        private static bool IsEmpty(DocIdSet set)
        {
            return set == null;
        }
        
        private static FixedBitSet ToFixedBitSet(DocIdSetIterator iterator, int numBits)
        {
            var set = new FixedBitSet(numBits);
            int doc;
            while ((doc = iterator.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
            {
                set.Set(doc);
            }
            return set;
        }

        // LUCENENET NOTE: This was value(int) in Lucene.
        public override IComparable this[int slot] => _wrappedComparer[slot];

        /// <summary>
        /// Concrete implementation of <see cref="ToParentBlockJoinSortField"/> to sorts the parent docs with the lowest values
        /// in the child / nested docs first.
        /// </summary>
        public sealed class Lowest : ToParentBlockJoinFieldComparer
        {
            /// <summary>
            /// Create <see cref="ToParentBlockJoinFieldComparer.Lowest"/>
            /// </summary>
            /// <param name="wrappedComparer">The <see cref="FieldComparer"/> on the child / nested level. </param>
            /// <param name="parentFilter"><see cref="Filter"/> (must produce <see cref="FixedBitSet"/> per-segment) that identifies the parent documents. </param>
            /// <param name="childFilter"><see cref="Filter"/> that defines which child / nested documents participates in sorting. </param>
            /// <param name="spareSlot">The extra slot inside the wrapped comparer that is used to compare which nested document
            ///                  inside the parent document scope is most competitive. </param>
            public Lowest(FieldComparer wrappedComparer, Filter parentFilter, Filter childFilter, int spareSlot) 
                : base(wrappedComparer, parentFilter, childFilter, spareSlot)
            {
            }
            
            public override int CompareBottom(int parentDoc)
            {
                if (parentDoc == 0 || _parentDocuments == null || _childDocuments == null)
                {
                    return 0;
                }

                // We need to copy the lowest value from all child docs into slot.
                int prevParentDoc = _parentDocuments.PrevSetBit(parentDoc - 1);
                int childDoc = _childDocuments.NextSetBit(prevParentDoc + 1);
                if (childDoc >= parentDoc || childDoc == -1)
                {
                    return 0;
                }

                // We only need to emit a single cmp value for any matching child doc
                int cmp = _wrappedComparer.CompareBottom(childDoc);
                if (cmp > 0)
                {
                    return cmp;
                }

                while (true)
                {
                    childDoc = _childDocuments.NextSetBit(childDoc + 1);
                    if (childDoc >= parentDoc || childDoc == -1)
                    {
                        return cmp;
                    }
                    int cmp1 = _wrappedComparer.CompareBottom(childDoc);
                    if (cmp1 > 0)
                    {
                        return cmp1;
                    }
                    if (cmp1 == 0)
                    {
                        cmp = 0;
                    }
                }
            }
            
            public override void Copy(int slot, int parentDoc)
            {
                if (parentDoc == 0 || _parentDocuments == null || _childDocuments == null)
                {
                    return;
                }

                // We need to copy the lowest value from all child docs into slot.
                int prevParentDoc = _parentDocuments.PrevSetBit(parentDoc - 1);
                int childDoc = _childDocuments.NextSetBit(prevParentDoc + 1);
                if (childDoc >= parentDoc || childDoc == -1)
                {
                    return;
                }
                _wrappedComparer.Copy(_spareSlot, childDoc);
                _wrappedComparer.Copy(slot, childDoc);

                while (true)
                {
                    childDoc = _childDocuments.NextSetBit(childDoc + 1);
                    if (childDoc >= parentDoc || childDoc == -1)
                    {
                        return;
                    }
                    _wrappedComparer.Copy(_spareSlot, childDoc);
                    if (_wrappedComparer.Compare(_spareSlot, slot) < 0)
                    {
                        _wrappedComparer.Copy(slot, childDoc);
                    }
                }
            }
            
            public override int CompareTop(int parentDoc)
            {
                if (parentDoc == 0 || _parentDocuments == null || _childDocuments == null)
                {
                    return 0;
                }

                // We need to copy the lowest value from all nested docs into slot.
                int prevParentDoc = _parentDocuments.PrevSetBit(parentDoc - 1);
                int childDoc = _childDocuments.NextSetBit(prevParentDoc + 1);
                if (childDoc >= parentDoc || childDoc == -1)
                {
                    return 0;
                }

                // We only need to emit a single cmp value for any matching child doc
                int cmp = _wrappedComparer.CompareBottom(childDoc);
                if (cmp > 0)
                {
                    return cmp;
                }

                while (true)
                {
                    childDoc = _childDocuments.NextSetBit(childDoc + 1);
                    if (childDoc >= parentDoc || childDoc == -1)
                    {
                        return cmp;
                    }
                    int cmp1 = _wrappedComparer.CompareTop(childDoc);
                    if (cmp1 > 0)
                    {
                        return cmp1;
                    }
                    if (cmp1 == 0)
                    {
                        cmp = 0;
                    }
                }
            }

        }

        /// <summary>
        /// Concrete implementation of <see cref="ToParentBlockJoinSortField"/> to sorts the parent docs with the highest values
        /// in the child / nested docs first.
        /// </summary>
        public sealed class Highest : ToParentBlockJoinFieldComparer
        {
            /// <summary>
            /// Create <see cref="ToParentBlockJoinFieldComparer.Highest"/>
            /// </summary>
            /// <param name="wrappedComparer">The <see cref="FieldComparer"/> on the child / nested level. </param>
            /// <param name="parentFilter"><see cref="Filter"/> (must produce <see cref="FixedBitSet"/> per-segment) that identifies the parent documents. </param>
            /// <param name="childFilter"><see cref="Filter"/> that defines which child / nested documents participates in sorting. </param>
            /// <param name="spareSlot">The extra slot inside the wrapped comparer that is used to compare which nested document
            ///                  inside the parent document scope is most competitive. </param>
            public Highest(FieldComparer wrappedComparer, Filter parentFilter, Filter childFilter, int spareSlot) 
                : base(wrappedComparer, parentFilter, childFilter, spareSlot)
            {
            }
            
            public override int CompareBottom(int parentDoc)
            {
                if (parentDoc == 0 || _parentDocuments == null || _childDocuments == null)
                {
                    return 0;
                }

                int prevParentDoc = _parentDocuments.PrevSetBit(parentDoc - 1);
                int childDoc = _childDocuments.NextSetBit(prevParentDoc + 1);
                if (childDoc >= parentDoc || childDoc == -1)
                {
                    return 0;
                }

                int cmp = _wrappedComparer.CompareBottom(childDoc);
                if (cmp < 0)
                {
                    return cmp;
                }

                while (true)
                {
                    childDoc = _childDocuments.NextSetBit(childDoc + 1);
                    if (childDoc >= parentDoc || childDoc == -1)
                    {
                        return cmp;
                    }
                    int cmp1 = _wrappedComparer.CompareBottom(childDoc);
                    if (cmp1 < 0)
                    {
                        return cmp1;
                    }
                    else
                    {
                        if (cmp1 == 0)
                        {
                            cmp = 0;
                        }
                    }
                }
            }
            
            public override void Copy(int slot, int parentDoc)
            {
                if (parentDoc == 0 || _parentDocuments == null || _childDocuments == null)
                {
                    return;
                }

                int prevParentDoc = _parentDocuments.PrevSetBit(parentDoc - 1);
                int childDoc = _childDocuments.NextSetBit(prevParentDoc + 1);
                if (childDoc >= parentDoc || childDoc == -1)
                {
                    return;
                }
                _wrappedComparer.Copy(_spareSlot, childDoc);
                _wrappedComparer.Copy(slot, childDoc);

                while (true)
                {
                    childDoc = _childDocuments.NextSetBit(childDoc + 1);
                    if (childDoc >= parentDoc || childDoc == -1)
                    {
                        return;
                    }
                    _wrappedComparer.Copy(_spareSlot, childDoc);
                    if (_wrappedComparer.Compare(_spareSlot, slot) > 0)
                    {
                        _wrappedComparer.Copy(slot, childDoc);
                    }
                }
            }
            
            public override int CompareTop(int parentDoc)
            {
                if (parentDoc == 0 || _parentDocuments == null || _childDocuments == null)
                {
                    return 0;
                }

                int prevParentDoc = _parentDocuments.PrevSetBit(parentDoc - 1);
                int childDoc = _childDocuments.NextSetBit(prevParentDoc + 1);
                if (childDoc >= parentDoc || childDoc == -1)
                {
                    return 0;
                }

                int cmp = _wrappedComparer.CompareBottom(childDoc);
                if (cmp < 0)
                {
                    return cmp;
                }

                while (true)
                {
                    childDoc = _childDocuments.NextSetBit(childDoc + 1);
                    if (childDoc >= parentDoc || childDoc == -1)
                    {
                        return cmp;
                    }
                    int cmp1 = _wrappedComparer.CompareTop(childDoc);
                    if (cmp1 < 0)
                    {
                        return cmp1;
                    }
                    if (cmp1 == 0)
                    {
                        cmp = 0;
                    }
                }
            }
        }
    }
}