using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using BitSet = Lucene.Net.Util.OpenBitSet;

namespace Lucene.Net.Codecs.SimpleText
{
    /*
     * 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 ArrayUtil = Util.ArrayUtil;
    using BytesRef = Util.BytesRef;
    using CharsRef = Util.CharsRef;
    using ChecksumIndexInput = Store.ChecksumIndexInput;
    using Directory = Store.Directory;
    using IBits = Util.IBits;
    using IMutableBits = Util.IMutableBits;
    using IndexFileNames = Index.IndexFileNames;
    using IndexOutput = Store.IndexOutput;
    using IOContext = Store.IOContext;
    using IOUtils = Util.IOUtils;
    using SegmentCommitInfo = Index.SegmentCommitInfo;
    using StringHelper = Util.StringHelper;
    using UnicodeUtil = Util.UnicodeUtil;

    /// <summary>
    /// Reads/writes plain text live docs.
    /// <para>
    /// <b><font color="red">FOR RECREATIONAL USE ONLY</font></b>
    /// </para>
    /// @lucene.experimental
    /// </summary>
    public class SimpleTextLiveDocsFormat : LiveDocsFormat
    {
        internal const string LIVEDOCS_EXTENSION = "liv";

        internal static readonly BytesRef SIZE = new BytesRef("size ");
        internal static readonly BytesRef DOC = new BytesRef("  doc ");
        internal static readonly BytesRef END = new BytesRef("END");

        public override IMutableBits NewLiveDocs(int size)
        {
            return new SimpleTextMutableBits(size);
        }

        public override IMutableBits NewLiveDocs(IBits existing)
        {
            var bits = (SimpleTextBits) existing;
            return new SimpleTextMutableBits((BitSet)bits.bits.Clone(), bits.Length);
        }

        public override IBits ReadLiveDocs(Directory dir, SegmentCommitInfo info, IOContext context)
        {
            Debug.Assert(info.HasDeletions);
            var scratch = new BytesRef();
            var scratchUtf16 = new CharsRef();

            var fileName = IndexFileNames.FileNameFromGeneration(info.Info.Name, LIVEDOCS_EXTENSION, info.DelGen);
            ChecksumIndexInput input = null;
            var success = false;

            try
            {
                input = dir.OpenChecksumInput(fileName, context);

                SimpleTextUtil.ReadLine(input, scratch);
                Debug.Assert(StringHelper.StartsWith(scratch, SIZE));
                var size = ParseInt32At(scratch, SIZE.Length, scratchUtf16);

                var bits = new BitSet(size);

                SimpleTextUtil.ReadLine(input, scratch);
                while (!scratch.Equals(END))
                {
                    Debug.Assert(StringHelper.StartsWith(scratch, DOC));
                    var docid = ParseInt32At(scratch, DOC.Length, scratchUtf16);
                    bits.Set(docid);
                    SimpleTextUtil.ReadLine(input, scratch);
                }

                SimpleTextUtil.CheckFooter(input);

                success = true;
                return new SimpleTextBits(bits, size);
            }
            finally
            {
                if (success)
                {
                    IOUtils.Dispose(input);
                }
                else
                {
                    IOUtils.DisposeWhileHandlingException(input);
                }
            }
        }

        /// <summary>
        /// NOTE: This was parseIntAt() in Lucene.
        /// </summary>
        private int ParseInt32At(BytesRef bytes, int offset, CharsRef scratch)
        {
            UnicodeUtil.UTF8toUTF16(bytes.Bytes, bytes.Offset + offset, bytes.Length - offset, scratch);
            return ArrayUtil.ParseInt32(scratch.Chars, 0, scratch.Length);
        }

        public override void WriteLiveDocs(IMutableBits bits, Directory dir, SegmentCommitInfo info, int newDelCount,
            IOContext context)
        {
            var set = ((SimpleTextBits) bits).bits;
            var size = bits.Length;
            var scratch = new BytesRef();

            var fileName = IndexFileNames.FileNameFromGeneration(info.Info.Name, LIVEDOCS_EXTENSION, info.NextDelGen);
            IndexOutput output = null;
            var success = false;
            try
            {
                output = dir.CreateOutput(fileName, context);
                SimpleTextUtil.Write(output, SIZE);
                SimpleTextUtil.Write(output, Convert.ToString(size, CultureInfo.InvariantCulture), scratch);
                SimpleTextUtil.WriteNewline(output);

                for (int i = set.NextSetBit(0); i >= 0; i = set.NextSetBit(i + 1))
                {
                    SimpleTextUtil.Write(output, DOC);
                    SimpleTextUtil.Write(output, Convert.ToString(i, CultureInfo.InvariantCulture), scratch);
                    SimpleTextUtil.WriteNewline(output);
                }

                SimpleTextUtil.Write(output, END);
                SimpleTextUtil.WriteNewline(output);
                SimpleTextUtil.WriteChecksum(output, scratch);
                success = true;
            }
            finally
            {
                if (success)
                {
                    IOUtils.Dispose(output);
                }
                else
                {
                    IOUtils.DisposeWhileHandlingException(output);
                }
            }
        }

        public override void Files(SegmentCommitInfo info, ICollection<string> files)
        {
            if (info.HasDeletions)
            {
                files.Add(IndexFileNames.FileNameFromGeneration(info.Info.Name, LIVEDOCS_EXTENSION, info.DelGen));
            }
        }

        // read-only
        internal class SimpleTextBits : IBits
        {
            internal readonly BitSet bits;
            private readonly int size;

            internal SimpleTextBits(BitSet bits, int size)
            {
                this.bits = bits;
                this.size = size;
            }

            public virtual bool Get(int index)
            {
                return bits.Get(index);
            }

            public virtual int Length => size;
        }

        // read-write
        internal class SimpleTextMutableBits : SimpleTextBits, IMutableBits
        {

            internal SimpleTextMutableBits(int size) 
                : this(new BitSet(size), size)
            {
                bits.Set(0, size);
            }

            internal SimpleTextMutableBits(BitSet bits, int size) 
                : base(bits, size)
            {
            }

            public virtual void Clear(int bit)
            {
                bits.Clear(bit);
            }
        }
    }
}