/*
 * Created on 25-Jan-2006
 */
using Lucene.Net.Analysis;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Support;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using JCG = J2N.Collections.Generic;

namespace Lucene.Net.Queries.Mlt
{
    /*
     * 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 simple wrapper for <see cref="MoreLikeThis"/> for use in scenarios where a <see cref="Query"/> object is required eg
    /// in custom QueryParser extensions. At query.Rewrite() time the reader is used to construct the
    /// actual <see cref="MoreLikeThis"/> object and obtain the real <see cref="Query"/> object.
    /// </summary>
    public class MoreLikeThisQuery : Query
    {
        private string likeText;
        private string[] moreLikeFields;
        private Analyzer analyzer;
        private readonly string fieldName;
        private float percentTermsToMatch = 0.3f;
        private int minTermFrequency = 1;
        private int maxQueryTerms = 5;
        private ISet<string> stopWords = null;
        private int minDocFreq = -1;

        /// <param name="moreLikeFields"> fields used for similarity measure </param>
        public MoreLikeThisQuery(string likeText, string[] moreLikeFields, Analyzer analyzer, string fieldName)
        {
            this.likeText = likeText;
            this.moreLikeFields = moreLikeFields;
            this.analyzer = analyzer;
            this.fieldName = fieldName;
        }

        public override Query Rewrite(IndexReader reader)
        {
            var mlt = new MoreLikeThis(reader)
            {
                FieldNames = moreLikeFields,
                Analyzer = analyzer,
                MinTermFreq = minTermFrequency
            };

            if (MinDocFreq >= 0)
            {
                mlt.MinDocFreq = minDocFreq;
            }
            mlt.MaxQueryTerms = maxQueryTerms;
            mlt.StopWords = stopWords;
            var bq = (BooleanQuery)mlt.Like(new StringReader(likeText), fieldName);
            var clauses = bq.GetClauses();
            //make at least half the terms match
            bq.MinimumNumberShouldMatch = (int)(clauses.Length * percentTermsToMatch);
            return bq;
        }

        /// <summary>
        /// <see cref="Query.ToString(string)"/>
        /// </summary>
        public override string ToString(string field)
        {
            return "like:" + LikeText;
        }

        public virtual float PercentTermsToMatch
        {
            get => percentTermsToMatch;
            set => percentTermsToMatch = value;
        }

        public virtual Analyzer Analyzer
        {
            get => analyzer;
            set => analyzer = value;
        }

        public virtual string LikeText
        {
            get => likeText;
            set => likeText = value;
        }

        public virtual int MaxQueryTerms
        {
            get => maxQueryTerms;
            set => maxQueryTerms = value;
        }

        public virtual int MinTermFrequency
        {
            get => minTermFrequency;
            set => minTermFrequency = value;
        }

        [WritableArray]
        [SuppressMessage("Microsoft.Performance", "CA1819", Justification = "Lucene's design requires some writable array properties")]
        public virtual string[] MoreLikeFields
        {
            get => moreLikeFields;
            set => moreLikeFields = value;
        }

        public virtual ISet<string> StopWords
        {
            get => stopWords;
            set => stopWords = value;
        }

        public virtual int MinDocFreq
        {
            get => minDocFreq;
            set => minDocFreq = value;
        }

        public override int GetHashCode()
        {
            const int prime = 31;
            int result = base.GetHashCode();
            result = prime * result + ((analyzer == null) ? 0 : analyzer.GetHashCode());
            result = prime * result + ((fieldName == null) ? 0 : fieldName.GetHashCode());
            result = prime * result + ((likeText == null) ? 0 : likeText.GetHashCode());
            result = prime * result + maxQueryTerms;
            result = prime * result + minDocFreq;
            result = prime * result + minTermFrequency;
            result = prime * result + Arrays.GetHashCode(moreLikeFields);
            result = prime * result + J2N.BitConversion.SingleToInt32Bits(percentTermsToMatch);
            // LUCENENET specific: use structural equality comparison
            result = prime * result + ((stopWords == null) ? 0 : JCG.SetEqualityComparer<string>.Default.GetHashCode(stopWords));
            return result;
        }

        public override bool Equals(object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (!base.Equals(obj))
            {
                return false;
            }
            if (this.GetType() != obj.GetType())
            {
                return false;
            }
            var other = (MoreLikeThisQuery)obj;
            if (analyzer == null)
            {
                if (other.analyzer != null)
                {
                    return false;
                }
            }
            else if (!analyzer.Equals(other.analyzer))
            {
                return false;
            }
            if (fieldName == null)
            {
                if (other.fieldName != null)
                {
                    return false;
                }
            }
            else if (!fieldName.Equals(other.fieldName, StringComparison.Ordinal))
            {
                return false;
            }
            if (likeText == null)
            {
                if (other.likeText != null)
                {
                    return false;
                }
            }
            else if (!likeText.Equals(other.likeText, StringComparison.Ordinal))
            {
                return false;
            }
            if (maxQueryTerms != other.maxQueryTerms)
            {
                return false;
            }
            if (minDocFreq != other.minDocFreq)
            {
                return false;
            }
            if (minTermFrequency != other.minTermFrequency)
            {
                return false;
            }
            if (!Arrays.Equals(moreLikeFields, other.moreLikeFields))
            {
                return false;
            }
            if (J2N.BitConversion.SingleToInt32Bits(percentTermsToMatch) != J2N.BitConversion.SingleToInt32Bits(other.percentTermsToMatch))
            {
                return false;
            }
            if (stopWords == null)
            {
                if (other.stopWords != null)
                {
                    return false;
                }
            }
            // LUCENENET specific: use structural equality comparison
            else if (!JCG.SetEqualityComparer<string>.Default.Equals(stopWords, other.stopWords))
            {
                return false;
            }
            return true;
        }
    }
}