/* 
 * 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.
 */

using System;
using System.Linq;
using Lucene.Net.Support;
using Document = Lucene.Net.Documents.Document;
using FieldSelector = Lucene.Net.Documents.FieldSelector;
using MultiTermDocs = Lucene.Net.Index.DirectoryReader.MultiTermDocs;
using MultiTermEnum = Lucene.Net.Index.DirectoryReader.MultiTermEnum;
using MultiTermPositions = Lucene.Net.Index.DirectoryReader.MultiTermPositions;
using DefaultSimilarity = Lucene.Net.Search.DefaultSimilarity;

namespace Lucene.Net.Index
{
    
    /// <summary>An IndexReader which reads multiple indexes, appending 
    /// their content.
    /// </summary>
    public class MultiReader:IndexReader, System.ICloneable
    {
        protected internal IndexReader[] subReaders;
        private int[] starts; // 1st docno for each segment
        private bool[] decrefOnClose; // remember which subreaders to decRef on close
        private System.Collections.Generic.IDictionary<string, byte[]> normsCache = new HashMap<string,byte[]>();
        private int maxDoc = 0;
        private int numDocs = - 1;
        private bool hasDeletions = false;
        
        /// <summary> <p/>Construct a MultiReader aggregating the named set of (sub)readers.
        /// Directory locking for delete, undeleteAll, and setNorm operations is
        /// left to the subreaders. <p/>
        /// <p/>Note that all subreaders are closed if this Multireader is closed.<p/>
        /// </summary>
        /// <param name="subReaders">set of (sub)readers
        /// </param>
        /// <throws>  IOException </throws>
        public MultiReader(params IndexReader[] subReaders)
        {
            Initialize(subReaders, true);
        }
        
        /// <summary> <p/>Construct a MultiReader aggregating the named set of (sub)readers.
        /// Directory locking for delete, undeleteAll, and setNorm operations is
        /// left to the subreaders. <p/>
        /// </summary>
        /// <param name="closeSubReaders">indicates whether the subreaders should be closed
        /// when this MultiReader is closed
        /// </param>
        /// <param name="subReaders">set of (sub)readers
        /// </param>
        /// <throws>  IOException </throws>
        public MultiReader(IndexReader[] subReaders, bool closeSubReaders)
        {
            Initialize(subReaders, closeSubReaders);
        }
        
        private void  Initialize(IndexReader[] subReaders, bool closeSubReaders)
        {
            // Deep copy
            this.subReaders = subReaders.ToArray();
            starts = new int[subReaders.Length + 1]; // build starts array
            decrefOnClose = new bool[subReaders.Length];
            for (int i = 0; i < subReaders.Length; i++)
            {
                starts[i] = maxDoc;
                maxDoc += subReaders[i].MaxDoc; // compute maxDocs
                
                if (!closeSubReaders)
                {
                    subReaders[i].IncRef();
                    decrefOnClose[i] = true;
                }
                else
                {
                    decrefOnClose[i] = false;
                }
                
                if (subReaders[i].HasDeletions)
                    hasDeletions = true;
            }
            starts[subReaders.Length] = maxDoc;
        }
        
        /// <summary> Tries to reopen the subreaders.
        /// <br/>
        /// If one or more subreaders could be re-opened (i. e. subReader.reopen() 
        /// returned a new instance != subReader), then a new MultiReader instance 
        /// is returned, otherwise this instance is returned.
        /// <p/>
        /// A re-opened instance might share one or more subreaders with the old 
        /// instance. Index modification operations result in undefined behavior
        /// when performed before the old instance is closed.
        /// (see <see cref="IndexReader.Reopen()" />).
        /// <p/>
        /// If subreaders are shared, then the reference count of those
        /// readers is increased to ensure that the subreaders remain open
        /// until the last referring reader is closed.
        /// 
        /// </summary>
        /// <throws>  CorruptIndexException if the index is corrupt </throws>
        /// <throws>  IOException if there is a low-level IO error  </throws>
        public override IndexReader Reopen()
        {
            lock (this)
            {
                return DoReopen(false);
            }
        }
        
        /// <summary> Clones the subreaders.
        /// (see <see cref="IndexReader.Clone()" />).
        /// <br/>
        /// <p/>
        /// If subreaders are shared, then the reference count of those
        /// readers is increased to ensure that the subreaders remain open
        /// until the last referring reader is closed.
        /// </summary>
        public override System.Object Clone()
        {
            try
            {
                return DoReopen(true);
            }
            catch (System.Exception ex)
            {
                throw new System.SystemException(ex.Message, ex);
            }
        }
        
        /// <summary> If clone is true then we clone each of the subreaders</summary>
        /// <param name="doClone">
        /// </param>
        /// <returns> New IndexReader, or same one (this) if
        /// reopen/clone is not necessary
        /// </returns>
        /// <throws>  CorruptIndexException </throws>
        /// <throws>  IOException </throws>
        protected internal virtual IndexReader DoReopen(bool doClone)
        {
            EnsureOpen();
            
            bool reopened = false;
            IndexReader[] newSubReaders = new IndexReader[subReaders.Length];
            
            bool success = false;
            try
            {
                for (int i = 0; i < subReaders.Length; i++)
                {
                    if (doClone)
                        newSubReaders[i] = (IndexReader) subReaders[i].Clone();
                    else
                        newSubReaders[i] = subReaders[i].Reopen();
                    // if at least one of the subreaders was updated we remember that
                    // and return a new MultiReader
                    if (newSubReaders[i] != subReaders[i])
                    {
                        reopened = true;
                    }
                }
                success = true;
            }
            finally
            {
                if (!success && reopened)
                {
                    for (int i = 0; i < newSubReaders.Length; i++)
                    {
                        if (newSubReaders[i] != subReaders[i])
                        {
                            try
                            {
                                newSubReaders[i].Close();
                            }
                            catch (System.IO.IOException)
                            {
                                // keep going - we want to clean up as much as possible
                            }
                        }
                    }
                }
            }
            
            if (reopened)
            {
                bool[] newDecrefOnClose = new bool[subReaders.Length];
                for (int i = 0; i < subReaders.Length; i++)
                {
                    if (newSubReaders[i] == subReaders[i])
                    {
                        newSubReaders[i].IncRef();
                        newDecrefOnClose[i] = true;
                    }
                }
                MultiReader mr = new MultiReader(newSubReaders);
                mr.decrefOnClose = newDecrefOnClose;
                return mr;
            }
            else
            {
                return this;
            }
        }
        
        public override ITermFreqVector[] GetTermFreqVectors(int n)
        {
            EnsureOpen();
            int i = ReaderIndex(n); // find segment num
            return subReaders[i].GetTermFreqVectors(n - starts[i]); // dispatch to segment
        }
        
        public override ITermFreqVector GetTermFreqVector(int n, System.String field)
        {
            EnsureOpen();
            int i = ReaderIndex(n); // find segment num
            return subReaders[i].GetTermFreqVector(n - starts[i], field);
        }
        
        
        public override void  GetTermFreqVector(int docNumber, System.String field, TermVectorMapper mapper)
        {
            EnsureOpen();
            int i = ReaderIndex(docNumber); // find segment num
            subReaders[i].GetTermFreqVector(docNumber - starts[i], field, mapper);
        }
        
        public override void  GetTermFreqVector(int docNumber, TermVectorMapper mapper)
        {
            EnsureOpen();
            int i = ReaderIndex(docNumber); // find segment num
            subReaders[i].GetTermFreqVector(docNumber - starts[i], mapper);
        }

        public override bool IsOptimized()
        {
            return false;
        }

        public override int NumDocs()
        {
            // Don't call ensureOpen() here (it could affect performance)
            // NOTE: multiple threads may wind up init'ing
            // numDocs... but that's harmless
            if (numDocs == - 1)
            {
                // check cache
                int n = 0; // cache miss--recompute
                for (int i = 0; i < subReaders.Length; i++)
                    n += subReaders[i].NumDocs(); // sum from readers
                numDocs = n;
            }
            return numDocs;
        }

        public override int MaxDoc
        {
            get
            {
                // Don't call ensureOpen() here (it could affect performance)
                return maxDoc;
            }
        }

        // inherit javadoc
        public override Document Document(int n, FieldSelector fieldSelector)
        {
            EnsureOpen();
            int i = ReaderIndex(n); // find segment num
            return subReaders[i].Document(n - starts[i], fieldSelector); // dispatch to segment reader
        }
        
        public override bool IsDeleted(int n)
        {
            // Don't call ensureOpen() here (it could affect performance)
            int i = ReaderIndex(n); // find segment num
            return subReaders[i].IsDeleted(n - starts[i]); // dispatch to segment reader
        }

        public override bool HasDeletions
        {
            get
            {
                // Don't call ensureOpen() here (it could affect performance)
                return hasDeletions;
            }
        }

        protected internal override void  DoDelete(int n)
        {
            numDocs = - 1; // invalidate cache
            int i = ReaderIndex(n); // find segment num
            subReaders[i].DeleteDocument(n - starts[i]); // dispatch to segment reader
            hasDeletions = true;
        }
        
        protected internal override void  DoUndeleteAll()
        {
            for (int i = 0; i < subReaders.Length; i++)
                subReaders[i].UndeleteAll();
            
            hasDeletions = false;
            numDocs = - 1; // invalidate cache
        }
        
        private int ReaderIndex(int n)
        {
            // find reader for doc n:
            return DirectoryReader.ReaderIndex(n, this.starts, this.subReaders.Length);
        }
        
        public override bool HasNorms(System.String field)
        {
            EnsureOpen();
            for (int i = 0; i < subReaders.Length; i++)
            {
                if (subReaders[i].HasNorms(field))
                    return true;
            }
            return false;
        }
        
        public override byte[] Norms(System.String field)
        {
            lock (this)
            {
                EnsureOpen();
                byte[] bytes = normsCache[field];
                if (bytes != null)
                    return bytes; // cache hit
                if (!HasNorms(field))
                    return null;
                
                bytes = new byte[MaxDoc];
                for (int i = 0; i < subReaders.Length; i++)
                    subReaders[i].Norms(field, bytes, starts[i]);
                normsCache[field] = bytes; // update cache
                return bytes;
            }
        }
        
        public override void  Norms(System.String field, byte[] result, int offset)
        {
            lock (this)
            {
                EnsureOpen();
                byte[] bytes = normsCache[field];
                for (int i = 0; i < subReaders.Length; i++)
                // read from segments
                    subReaders[i].Norms(field, result, offset + starts[i]);
                
                if (bytes == null && !HasNorms(field))
                {
                    for (int i = offset; i < result.Length; i++)
                    {
                        result[i] = (byte) DefaultSimilarity.EncodeNorm(1.0f);
                    }
                }
                else if (bytes != null)
                {
                    // cache hit
                    Array.Copy(bytes, 0, result, offset, MaxDoc);
                }
                else
                {
                    for (int i = 0; i < subReaders.Length; i++)
                    {
                        // read from segments
                        subReaders[i].Norms(field, result, offset + starts[i]);
                    }
                }
            }
        }
        
        protected internal override void  DoSetNorm(int n, System.String field, byte value_Renamed)
        {
            lock (normsCache)
            {
                normsCache.Remove(field); // clear cache
            }
            int i = ReaderIndex(n); // find segment num
            subReaders[i].SetNorm(n - starts[i], field, value_Renamed); // dispatch
        }
        
        public override TermEnum Terms()
        {
            EnsureOpen();
            return new MultiTermEnum(this, subReaders, starts, null);
        }
        
        public override TermEnum Terms(Term term)
        {
            EnsureOpen();
            return new MultiTermEnum(this, subReaders, starts, term);
        }
        
        public override int DocFreq(Term t)
        {
            EnsureOpen();
            int total = 0; // sum freqs in segments
            for (int i = 0; i < subReaders.Length; i++)
                total += subReaders[i].DocFreq(t);
            return total;
        }
        
        public override TermDocs TermDocs()
        {
            EnsureOpen();
            return new MultiTermDocs(this, subReaders, starts);
        }
        
        public override TermPositions TermPositions()
        {
            EnsureOpen();
            return new MultiTermPositions(this, subReaders, starts);
        }

        protected internal override void DoCommit(System.Collections.Generic.IDictionary<string, string> commitUserData)
        {
            for (int i = 0; i < subReaders.Length; i++)
                subReaders[i].Commit(commitUserData);
        }
        
        protected internal override void  DoClose()
        {
            lock (this)
            {
                for (int i = 0; i < subReaders.Length; i++)
                {
                    if (decrefOnClose[i])
                    {
                        subReaders[i].DecRef();
                    }
                    else
                    {
                        subReaders[i].Close();
                    }
                }
            }

            // NOTE: only needed in case someone had asked for
            // FieldCache for top-level reader (which is generally
            // not a good idea):
            Lucene.Net.Search.FieldCache_Fields.DEFAULT.Purge(this);
        }

        public override System.Collections.Generic.ICollection<string> GetFieldNames(IndexReader.FieldOption fieldNames)
        {
            EnsureOpen();
            return DirectoryReader.GetFieldNames(fieldNames, this.subReaders);
        }

        /// <summary> Checks recursively if all subreaders are up to date. </summary>
        public override bool IsCurrent()
        {
            for (int i = 0; i < subReaders.Length; i++)
            {
                if (!subReaders[i].IsCurrent())
                {
                    return false;
                }
            }

            // all subreaders are up to date
            return true;
        }

        /// <summary>Not implemented.</summary>
        /// <throws>  UnsupportedOperationException </throws>
        public override long Version
        {
            get { throw new System.NotSupportedException("MultiReader does not support this method."); }
        }

        public override IndexReader[] GetSequentialSubReaders()
        {
            return subReaders;
        }
    }
}