using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Support;
using Lucene.Net.Util;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace Lucene.Net.Queries
{
    /*
     * 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 container <see cref="Filter"/> that allows Boolean composition of <see cref="Filter"/>s.
    /// <see cref="Filter"/>s are allocated into one of three logical constructs;
    /// SHOULD, MUST NOT, MUST
    /// The results <see cref="Filter"/> BitSet is constructed as follows:
    /// SHOULD Filters are OR'd together
    /// The resulting <see cref="Filter"/> is NOT'd with the NOT <see cref="Filter"/>s
    /// The resulting <see cref="Filter"/> is AND'd with the MUST <see cref="Filter"/>s
    /// </summary>
    public class BooleanFilter : Filter, IEnumerable<FilterClause>
    {
        private readonly IList<FilterClause> clauses = new EquatableList<FilterClause>();

        /// <summary>
        /// Returns the a <see cref="DocIdSetIterator"/> representing the Boolean composition
        /// of the filters that have been added.
        /// </summary>
        public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits acceptDocs)
        {
            FixedBitSet res = null;
            AtomicReader reader = context.AtomicReader;

            bool hasShouldClauses = false;
            foreach (FilterClause fc in clauses)
            {
                if (fc.Occur == Occur.SHOULD)
                {
                    hasShouldClauses = true;
                    DocIdSetIterator disi = GetDISI(fc.Filter, context);
                    if (disi == null)
                    {
                        continue;
                    }
                    if (res == null)
                    {
                        res = new FixedBitSet(reader.MaxDoc);
                    }
                    res.Or(disi);
                }
            }
            if (hasShouldClauses && res == null)
            {
                return null;
            }

            foreach (FilterClause fc in clauses)
            {
                if (fc.Occur == Occur.MUST_NOT)
                {
                    if (res == null)
                    {
                        Debug.Assert(!hasShouldClauses);
                        res = new FixedBitSet(reader.MaxDoc);
                        res.Set(0, reader.MaxDoc); // NOTE: may set bits on deleted docs
                    }

                    DocIdSetIterator disi = GetDISI(fc.Filter, context);
                    if (disi != null)
                    {
                        res.AndNot(disi);
                    }
                }
            }

            foreach (FilterClause fc in clauses)
            {
                if (fc.Occur == Occur.MUST)
                {
                    DocIdSetIterator disi = GetDISI(fc.Filter, context);
                    if (disi == null)
                    {
                        return null; // no documents can match
                    }
                    if (res == null)
                    {
                        res = new FixedBitSet(reader.MaxDoc);
                        res.Or(disi);
                    }
                    else
                    {
                        res.And(disi);
                    }
                }
            }

            return BitsFilteredDocIdSet.Wrap(res, acceptDocs);
        }

        private static DocIdSetIterator GetDISI(Filter filter, AtomicReaderContext context)
        {
            // we dont pass acceptDocs, we will filter at the end using an additional filter
            DocIdSet set = filter.GetDocIdSet(context, null);
            return set == null ? null : set.GetIterator();
        }

        /// <summary>
        /// Adds a new <see cref="FilterClause"/> to the Boolean <see cref="Filter"/> container </summary>
        /// <param name="filterClause"> A <see cref="FilterClause"/> object containing a <see cref="Filter"/> and an <see cref="Occur"/> parameter </param>
        public virtual void Add(FilterClause filterClause)
        {
            clauses.Add(filterClause);
        }

        public void Add(Filter filter, Occur occur)
        {
            Add(new FilterClause(filter, occur));
        }

        /// <summary>
        /// Gets the list of clauses
        /// </summary>
        public virtual IList<FilterClause> Clauses
        {
            get { return clauses; }
        }

        /// <summary>
        /// Returns an iterator on the clauses in this query. It implements the <see cref="IEnumerable{T}"/> interface to
        /// make it possible to do:
        /// <code>for (FilterClause clause : booleanFilter) {}</code>
        /// </summary>
        public IEnumerator<FilterClause> GetEnumerator()
        {
            return Clauses.GetEnumerator();
        }

        public override bool Equals(object obj)
        {
            if (this == obj)
            {
                return true;
            }

            if ((obj == null) || (obj.GetType() != this.GetType()))
            {
                return false;
            }

            var other = (BooleanFilter)obj;
            return clauses.Equals(other.clauses);
        }

        public override int GetHashCode()
        {
            return 657153718 ^ clauses.GetHashCode();
        }

        /// <summary>
        /// Prints a user-readable version of this <see cref="Filter"/>. </summary>
        public override string ToString()
        {
            var buffer = new StringBuilder("BooleanFilter(");
            int minLen = buffer.Length;
            foreach (FilterClause c in clauses)
            {
                if (buffer.Length > minLen)
                {
                    buffer.Append(' ');
                }
                buffer.Append(c);
            }
            return buffer.Append(')').ToString();
        }

        // LUCENENET specific
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}