using Lucene.Net.Index;
using Lucene.Net.Store;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using Directory = Lucene.Net.Store.Directory;

namespace Lucene.Net.Replicator
{
    /*
	 * 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 <see cref="IRevision"/> of a single index files which comprises the list of files
    /// that are part of the current <see cref="IndexCommit"/>. To ensure the files are not
    /// deleted by <see cref="IndexWriter"/> for as long as this revision stays alive (i.e.
    /// until <see cref="Release"/>, the current commit point is snapshotted, using
    /// <see cref="SnapshotDeletionPolicy"/> (this means that the given writer's
    /// <see cref="IndexWriterConfig.IndexDeletionPolicy"/> should return
    /// <see cref="SnapshotDeletionPolicy"/>).
    /// <para/>
    /// When this revision is <see cref="Release()"/>d, it releases the obtained
    /// snapshot as well as calls <see cref="IndexWriter.DeleteUnusedFiles"/> so that the
    /// snapshotted files are deleted (if they are no longer needed).
    /// </summary>
    /// <remarks>
    /// @lucene.experimental
    /// </remarks>
    public class IndexRevision : IRevision
    {
        private const string SOURCE = "index";

        private readonly IndexWriter writer;
        private readonly IndexCommit commit;
        private readonly SnapshotDeletionPolicy sdp;
        private readonly string version;
        private readonly IDictionary<string, IList<RevisionFile>> sourceFiles;

        // returns a RevisionFile with some metadata
        private static RevisionFile CreateRevisionFile(string fileName, Directory directory)
        {
            return new RevisionFile(fileName, directory.FileLength(fileName));
        }

        /// <summary>
        /// Returns a singleton map of the revision files from the given <see cref="IndexCommit"/>.
        /// </summary>
        public static IDictionary<string, IList<RevisionFile>> RevisionFiles(IndexCommit commit)
        {
            List<RevisionFile> revisionFiles = commit.FileNames
                .Where(file => !string.Equals(file, commit.SegmentsFileName, StringComparison.Ordinal))
                .Select(file => CreateRevisionFile(file, commit.Directory))
                //Note: segments_N must be last
                .Union(new[] {CreateRevisionFile(commit.SegmentsFileName, commit.Directory)})
                .ToList();
            return new Dictionary<string, IList<RevisionFile>>
            {
                { SOURCE, revisionFiles }
            };
        }
   
        /// <summary>
        /// Returns a string representation of a revision's version from the given 
        /// <see cref="IndexCommit"/>
        /// </summary>
        public static string RevisionVersion(IndexCommit commit)
        {
            return commit.Generation.ToString("X");
        }

        /// <summary>
        /// Constructor over the given <see cref="IndexWriter"/>. Uses the last
        /// <see cref="IndexCommit"/> found in the <see cref="Directory"/> managed by the given
        /// writer.
        /// </summary>
        public IndexRevision(IndexWriter writer)
        {
            sdp = writer.Config.IndexDeletionPolicy as SnapshotDeletionPolicy;
            if (sdp == null)
                throw new ArgumentException("IndexWriter must be created with SnapshotDeletionPolicy", "writer");

            this.writer = writer;
            this.commit = sdp.Snapshot();
            this.version = RevisionVersion(commit);
            this.sourceFiles = RevisionFiles(commit);
        }

        public virtual int CompareTo(string version)
        {
            long gen = long.Parse(version, NumberStyles.HexNumber);
            long commitGen = commit.Generation;
            //TODO: long.CompareTo(); but which goes where.
            return commitGen < gen ? -1 : (commitGen > gen ? 1 : 0);
        }

        public virtual int CompareTo(IRevision other)
        {
            //TODO: This breaks the contract and will fail if called with a different implementation
            //      This is a flaw inherited from the original source...
            //      It should at least provide a better description to the InvalidCastException
            IndexRevision or = (IndexRevision)other;
            return commit.CompareTo(or.commit);
        }

        public virtual string Version { get { return version; } }

        public virtual IDictionary<string, IList<RevisionFile>> SourceFiles { get { return sourceFiles; } }

        public virtual Stream Open(string source, string fileName)
        {
            Debug.Assert(source.Equals(SOURCE, StringComparison.Ordinal), string.Format("invalid source; expected={0} got={1}", SOURCE, source));
            return new IndexInputStream(commit.Directory.OpenInput(fileName, IOContext.READ_ONCE));
        }

        public virtual void Release()
        {
            sdp.Release(commit);
            writer.DeleteUnusedFiles();
        }

        public override string ToString()
        {
            return "IndexRevision version=" + Version + " files=" + SourceFiles;
        }
    }
}