using Lucene.Net.Search;
using System;
using System.IO;

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>
    /// Utility for query time joining using <see cref="TermsQuery"/> and <see cref="TermsCollector"/>.
    /// 
    /// @lucene.experimental
    /// </summary>
    public sealed class JoinUtil
    {
        // No instances allowed
        private JoinUtil()
        {
        }

        /// <summary>
        /// Method for query time joining.
        /// <para/>
        /// Execute the returned query with a <see cref="IndexSearcher"/> to retrieve all documents that have the same terms in the
        /// to field that match with documents matching the specified <paramref name="fromQuery"/> and have the same terms in the from field.
        /// <para/>
        /// In the case a single document relates to more than one document the <paramref name="multipleValuesPerDocument"/> option
        /// should be set to true. When the <paramref name="multipleValuesPerDocument"/> is set to <c>true</c> only the
        /// the score from the first encountered join value originating from the 'from' side is mapped into the 'to' side.
        /// Even in the case when a second join value related to a specific document yields a higher score. Obviously this
        /// doesn't apply in the case that <see cref="ScoreMode.None"/> is used, since no scores are computed at all.
        /// <para/>
        /// Memory considerations: During joining all unique join values are kept in memory. On top of that when the <paramref name="scoreMode"/>
        /// isn't set to <see cref="ScoreMode.None"/> a float value per unique join value is kept in memory for computing scores.
        /// When <paramref name="scoreMode"/> is set to <see cref="ScoreMode.Avg"/> also an additional integer value is kept in memory per unique
        /// join value.
        /// </summary>
        /// <param name="fromField">                 The from field to join from </param>
        /// <param name="multipleValuesPerDocument"> Whether the from field has multiple terms per document </param>
        /// <param name="toField">                   The to field to join to </param>
        /// <param name="fromQuery">                 The query to match documents on the from side </param>
        /// <param name="fromSearcher">              The searcher that executed the specified <paramref name="fromQuery"/> </param>
        /// <param name="scoreMode">                 Instructs how scores from the <paramref name="fromQuery"/> are mapped to the returned query </param>
        /// <returns>A <see cref="Query"/> instance that can be used to join documents based on the terms in the from and to field</returns>
        /// <exception cref="IOException"> If I/O related errors occur </exception>
        public static Query CreateJoinQuery(string fromField, bool multipleValuesPerDocument, string toField, Query fromQuery, IndexSearcher fromSearcher, ScoreMode scoreMode)
        {
            switch (scoreMode)
            {
                case ScoreMode.None:
                    TermsCollector termsCollector = TermsCollector.Create(fromField, multipleValuesPerDocument);
                    fromSearcher.Search(fromQuery, termsCollector);
                    return new TermsQuery(toField, fromQuery, termsCollector.CollectorTerms);
                case ScoreMode.Total:
                case ScoreMode.Max:
                case ScoreMode.Avg:
                    TermsWithScoreCollector termsWithScoreCollector = TermsWithScoreCollector.Create(fromField, multipleValuesPerDocument, scoreMode);
                    fromSearcher.Search(fromQuery, termsWithScoreCollector);
                    return new TermsIncludingScoreQuery(toField, multipleValuesPerDocument, termsWithScoreCollector.CollectedTerms, termsWithScoreCollector.ScoresPerTerm, fromQuery);
                default:
                    throw new ArgumentException(string.Format("Score mode {0} isn't supported.", scoreMode));
            }
        }
    }
}