using Lucene.Net.Util;
using System;
using System.Collections.Generic;
using System.IO;

namespace Lucene.Net.Index
{
    /*
     * 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 CommandLineUtil = Lucene.Net.Util.CommandLineUtil;
    using Constants = Lucene.Net.Util.Constants;
    using Directory = Lucene.Net.Store.Directory;
    using FSDirectory = Lucene.Net.Store.FSDirectory;
    using InfoStream = Lucene.Net.Util.InfoStream;

    /// <summary>
    /// This is an easy-to-use tool that upgrades all segments of an index from previous Lucene versions
    /// to the current segment file format. It can be used from command line:
    /// <code>
    ///  java -cp lucene-core.jar Lucene.Net.Index.IndexUpgrader [-delete-prior-commits] [-verbose] indexDir
    /// </code>
    /// Alternatively this class can be instantiated and <see cref="Upgrade()"/> invoked. It uses <see cref="UpgradeIndexMergePolicy"/>
    /// and triggers the upgrade via an <see cref="IndexWriter.ForceMerge(int)"/> request to <see cref="IndexWriter"/>.
    /// <para/>
    /// This tool keeps only the last commit in an index; for this
    /// reason, if the incoming index has more than one commit, the tool
    /// refuses to run by default. Specify <c>-delete-prior-commits</c>
    /// to override this, allowing the tool to delete all but the last commit.
    /// From .NET code this can be enabled by passing <c>true</c> to
    /// <see cref="IndexUpgrader(Directory, LuceneVersion, TextWriter, bool)"/>.
    /// <para/>
    /// <b>Warning:</b> this tool may reorder documents if the index was partially
    /// upgraded before execution (e.g., documents were added). If your application relies
    /// on &quot;monotonicity&quot; of doc IDs (which means that the order in which the documents
    /// were added to the index is preserved), do a full ForceMerge instead.
    /// The <see cref="MergePolicy"/> set by <see cref="IndexWriterConfig"/> may also reorder
    /// documents.
    /// </summary>
    public sealed class IndexUpgrader
    {
        private static void PrintUsage()
        {
            Console.Error.WriteLine("Upgrades an index so all segments created with a previous Lucene version are rewritten.");
            Console.Error.WriteLine("Usage:");
            Console.Error.WriteLine("  java " + typeof(IndexUpgrader).Name + " [-delete-prior-commits] [-verbose] [-dir-impl X] indexDir");
            Console.Error.WriteLine("this tool keeps only the last commit in an index; for this");
            Console.Error.WriteLine("reason, if the incoming index has more than one commit, the tool");
            Console.Error.WriteLine("refuses to run by default. Specify -delete-prior-commits to override");
            Console.Error.WriteLine("this, allowing the tool to delete all but the last commit.");
            Console.Error.WriteLine("Specify a " + typeof(FSDirectory).Name + " implementation through the -dir-impl option to force its use. If no package is specified the " + typeof(FSDirectory).Namespace + " package will be used.");
            Console.Error.WriteLine("WARNING: this tool may reorder document IDs!");
            Environment.FailFast("1");
        }

        /// <summary>
        /// Main method to run <see cref="IndexUpgrader"/> from the
        /// command-line.
        /// </summary>
        public static void Main(string[] args)
        {
            ParseArgs(args).Upgrade();
        }

        public static IndexUpgrader ParseArgs(string[] args)
        {
            string path = null;
            bool deletePriorCommits = false;
            TextWriter @out = null;
            string dirImpl = null;
            int i = 0;
            while (i < args.Length)
            {
                string arg = args[i];
                if ("-delete-prior-commits".Equals(arg, StringComparison.Ordinal))
                {
                    deletePriorCommits = true;
                }
                else if ("-verbose".Equals(arg, StringComparison.Ordinal))
                {
                    @out = Console.Out;
                }
                else if ("-dir-impl".Equals(arg, StringComparison.Ordinal))
                {
                    if (i == args.Length - 1)
                    {
                        Console.WriteLine("ERROR: missing value for -dir-impl option");
                        Environment.FailFast("1");
                    }
                    i++;
                    dirImpl = args[i];
                }
                else if (path == null)
                {
                    path = arg;
                }
                else
                {
                    PrintUsage();
                }
                i++;
            }
            if (path == null)
            {
                PrintUsage();
            }

            Directory dir = null;
            if (dirImpl == null)
            {
                dir = FSDirectory.Open(new DirectoryInfo(path));
            }
            else
            {
                dir = CommandLineUtil.NewFSDirectory(dirImpl, new DirectoryInfo(path));
            }
#pragma warning disable 612, 618
            return new IndexUpgrader(dir, LuceneVersion.LUCENE_CURRENT, @out, deletePriorCommits);
#pragma warning restore 612, 618
        }

        private readonly Directory dir;
        private readonly IndexWriterConfig iwc;
        private readonly bool deletePriorCommits;

        /// <summary>
        /// Creates index upgrader on the given directory, using an <see cref="IndexWriter"/> using the given
        /// <paramref name="matchVersion"/>. The tool refuses to upgrade indexes with multiple commit points.
        /// </summary>
        public IndexUpgrader(Directory dir, LuceneVersion matchVersion)
            : this(dir, new IndexWriterConfig(matchVersion, null), false)
        {
        }

        /// <summary>
        /// Creates index upgrader on the given directory, using an <see cref="IndexWriter"/> using the given
        /// <paramref name="matchVersion"/>. You have the possibility to upgrade indexes with multiple commit points by removing
        /// all older ones. If <paramref name="infoStream"/> is not <c>null</c>, all logging output will be sent to this stream.
        /// </summary>
        public IndexUpgrader(Directory dir, LuceneVersion matchVersion, TextWriter infoStream, bool deletePriorCommits)
            : this(dir, new IndexWriterConfig(matchVersion, null), deletePriorCommits)
        {
            if (null != infoStream)
            {
                this.iwc.SetInfoStream(infoStream);
            }
        }

        /// <summary>
        /// Creates index upgrader on the given directory, using an <see cref="IndexWriter"/> using the given
        /// config. You have the possibility to upgrade indexes with multiple commit points by removing
        /// all older ones.
        /// </summary>
        public IndexUpgrader(Directory dir, IndexWriterConfig iwc, bool deletePriorCommits)
        {
            this.dir = dir;
            this.iwc = iwc;
            this.deletePriorCommits = deletePriorCommits;
        }

        /// <summary>
        /// Perform the upgrade. </summary>
        public void Upgrade()
        {
            if (!DirectoryReader.IndexExists(dir))
            {
                throw new IndexNotFoundException(dir.ToString());
            }

            if (!deletePriorCommits)
            {
                ICollection<IndexCommit> commits = DirectoryReader.ListCommits(dir);
                if (commits.Count > 1)
                {
                    throw new System.ArgumentException("this tool was invoked to not delete prior commit points, but the following commits were found: " + commits);
                }
            }

            IndexWriterConfig c = (IndexWriterConfig)iwc.Clone();
            c.MergePolicy = new UpgradeIndexMergePolicy(c.MergePolicy);
            c.IndexDeletionPolicy = new KeepOnlyLastCommitDeletionPolicy();

            IndexWriter w = new IndexWriter(dir, c);
            try
            {
                InfoStream infoStream = c.InfoStream;
                if (infoStream.IsEnabled("IndexUpgrader"))
                {
                    infoStream.Message("IndexUpgrader", "Upgrading all pre-" + Constants.LUCENE_MAIN_VERSION + " segments of index directory '" + dir + "' to version " + Constants.LUCENE_MAIN_VERSION + "...");
                }
                w.ForceMerge(1);
                if (infoStream.IsEnabled("IndexUpgrader"))
                {
                    infoStream.Message("IndexUpgrader", "All segments upgraded to version " + Constants.LUCENE_MAIN_VERSION);
                }
            }
            finally
            {
                w.Dispose();
            }
        }
    }
}