using Lucene.Net.Diagnostics;
using System;
using System.Runtime.CompilerServices;

namespace Lucene.Net.Util
{
    /*
     * 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>
    /// <see cref="Sorter"/> implementation based on the
    /// <a href="http://svn.python.org/projects/python/trunk/Objects/listsort.txt">TimSort</a>
    /// algorithm.
    /// <para/>This implementation is especially good at sorting partially-sorted
    /// arrays and sorts small arrays with binary sort.
    /// <para/><b>NOTE</b>:There are a few differences with the original implementation:
    /// <list type="bullet">
    ///     <item><description><a name="maxTempSlots"/>The extra amount of memory to perform merges is
    ///         configurable. This allows small merges to be very fast while large merges
    ///         will be performed in-place (slightly slower). You can make sure that the
    ///         fast merge routine will always be used by having <c>maxTempSlots</c>
    ///         equal to half of the length of the slice of data to sort.</description></item>
    ///     <item><description>Only the fast merge routine can gallop (the one that doesn't run
    ///         in-place) and it only gallops on the longest slice.</description></item>
    /// </list>
    /// <para/>
    /// @lucene.internal
    /// </summary>
    public abstract class TimSorter : Sorter
    {
        internal const int MINRUN = 32;
        internal new const int THRESHOLD = 64;
        internal const int STACKSIZE = 40; // depends on MINRUN
        internal const int MIN_GALLOP = 7;

        internal readonly int maxTempSlots;
        internal int minRun;
        internal int to;
        internal int stackSize;
        internal int[] runEnds;

        /// <summary>
        /// Create a new <see cref="TimSorter"/>. </summary>
        /// <param name="maxTempSlots"> The <a href="#maxTempSlots">maximum amount of extra memory to run merges</a> </param>
        protected TimSorter(int maxTempSlots)
            : base()
        {
            runEnds = new int[1 + STACKSIZE];
            this.maxTempSlots = maxTempSlots;
        }

        /// <summary>
        /// Minimum run length for an array of length <paramref name="length"/>. </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static int MinRun(int length)
        {
            if (Debugging.AssertsEnabled) Debugging.Assert(length >= MINRUN);
            int n = length;
            int r = 0;
            while (n >= 64)
            {
                r |= n & 1;
                n = (int)((uint)n >> 1);
            }
            int minRun = n + r;
            if (Debugging.AssertsEnabled) Debugging.Assert(minRun >= MINRUN && minRun <= THRESHOLD);
            return minRun;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual int RunLen(int i)
        {
            int off = stackSize - i;
            return runEnds[off] - runEnds[off - 1];
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual int RunBase(int i)
        {
            return runEnds[stackSize - i - 1];
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual int RunEnd(int i) // LUCENENET TODO: API - change to indexer
        {
            return runEnds[stackSize - i];
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void SetRunEnd(int i, int runEnd)
        {
            runEnds[stackSize - i] = runEnd;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void PushRunLen(int len)
        {
            runEnds[stackSize + 1] = runEnds[stackSize] + len;
            ++stackSize;
        }

        /// <summary>
        /// Compute the length of the next run, make the run sorted and return its
        /// length.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual int NextRun()
        {
            int runBase = RunEnd(0);
            if (Debugging.AssertsEnabled) Debugging.Assert(runBase < to);
            if (runBase == to - 1)
            {
                return 1;
            }
            int o = runBase + 2;
            if (Compare(runBase, runBase + 1) > 0)
            {
                // run must be strictly descending
                while (o < to && Compare(o - 1, o) > 0)
                {
                    ++o;
                }
                Reverse(runBase, o);
            }
            else
            {
                // run must be non-descending
                while (o < to && Compare(o - 1, o) <= 0)
                {
                    ++o;
                }
            }
            int runHi = Math.Max(o, Math.Min(to, runBase + minRun));
            BinarySort(runBase, runHi, o);
            return runHi - runBase;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void EnsureInvariants()
        {
            while (stackSize > 1)
            {
                int runLen0 = RunLen(0);
                int runLen1 = RunLen(1);

                if (stackSize > 2)
                {
                    int runLen2 = RunLen(2);

                    if (runLen2 <= runLen1 + runLen0)
                    {
                        // merge the smaller of 0 and 2 with 1
                        if (runLen2 < runLen0)
                        {
                            MergeAt(1);
                        }
                        else
                        {
                            MergeAt(0);
                        }
                        continue;
                    }
                }

                if (runLen1 <= runLen0)
                {
                    MergeAt(0);
                    continue;
                }

                break;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void ExhaustStack()
        {
            while (stackSize > 1)
            {
                MergeAt(0);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void Reset(int from, int to)
        {
            stackSize = 0;
            Array.Clear(runEnds, 0, runEnds.Length);
            runEnds[0] = from;
            this.to = to;
            int length = to - from;
            this.minRun = length <= THRESHOLD ? length : MinRun(length);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void MergeAt(int n)
        {
            if (Debugging.AssertsEnabled) Debugging.Assert(stackSize >= 2);
            Merge(RunBase(n + 1), RunBase(n), RunEnd(n));
            for (int j = n + 1; j > 0; --j)
            {
                SetRunEnd(j, RunEnd(j - 1));
            }
            --stackSize;
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        internal virtual void Merge(int lo, int mid, int hi)
        {
            if (Compare(mid - 1, mid) <= 0)
            {
                return;
            }
            lo = Upper2(lo, mid, mid);
            hi = Lower2(mid, hi, mid - 1);

            if (hi - mid <= mid - lo && hi - mid <= maxTempSlots)
            {
                MergeHi(lo, mid, hi);
            }
            else if (mid - lo <= maxTempSlots)
            {
                MergeLo(lo, mid, hi);
            }
            else
            {
                MergeInPlace(lo, mid, hi);
            }
        }

        /// <summary>
        /// Sort the slice which starts at <paramref name="from"/> (inclusive) and ends at
        /// <paramref name="to"/> (exclusive).
        /// </summary>
        public override void Sort(int from, int to)
        {
            CheckRange(from, to);
            if (to - from <= 1)
            {
                return;
            }
            Reset(from, to);
            do
            {
                EnsureInvariants();
                PushRunLen(NextRun());
            } while (RunEnd(0) < to);
            ExhaustStack();
            if (Debugging.AssertsEnabled) Debugging.Assert(RunEnd(0) == to);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal override void DoRotate(int lo, int mid, int hi)
        {
            int len1 = mid - lo;
            int len2 = hi - mid;
            if (len1 == len2)
            {
                while (mid < hi)
                {
                    Swap(lo++, mid++);
                }
            }
            else if (len2 < len1 && len2 <= maxTempSlots)
            {
                Save(mid, len2);
                for (int i = lo + len1 - 1, j = hi - 1; i >= lo; --i, --j)
                {
                    Copy(i, j);
                }
                for (int i = 0, j = lo; i < len2; ++i, ++j)
                {
                    Restore(i, j);
                }
            }
            else if (len1 <= maxTempSlots)
            {
                Save(lo, len1);
                for (int i = mid, j = lo; i < hi; ++i, ++j)
                {
                    Copy(i, j);
                }
                for (int i = 0, j = lo + len2; j < hi; ++i, ++j)
                {
                    Restore(i, j);
                }
            }
            else
            {
                Reverse(lo, mid);
                Reverse(mid, hi);
                Reverse(lo, hi);
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void MergeLo(int lo, int mid, int hi)
        {
            if (Debugging.AssertsEnabled) Debugging.Assert(Compare(lo, mid) > 0);
            int len1 = mid - lo;
            Save(lo, len1);
            Copy(mid, lo);
            int i = 0, j = mid + 1, dest = lo + 1;
            for (; ; )
            {
                for (int count = 0; count < MIN_GALLOP; )
                {
                    if (i >= len1 || j >= hi)
                    {
                        goto outerBreak;
                    }
                    else if (CompareSaved(i, j) <= 0)
                    {
                        Restore(i++, dest++);
                        count = 0;
                    }
                    else
                    {
                        Copy(j++, dest++);
                        ++count;
                    }
                }
                // galloping...
                int next = LowerSaved3(j, hi, i);
                for (; j < next; ++dest)
                {
                    Copy(j++, dest);
                }
                Restore(i++, dest++);
            //outerContinue: ; // LUCENENET NOTE: Not referenced
            }
        outerBreak:
            for (; i < len1; ++dest)
            {
                Restore(i++, dest);
            }
            if (Debugging.AssertsEnabled) Debugging.Assert(j == dest);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual void MergeHi(int lo, int mid, int hi)
        {
            if (Debugging.AssertsEnabled) Debugging.Assert(Compare(mid - 1, hi - 1) > 0);
            int len2 = hi - mid;
            Save(mid, len2);
            Copy(mid - 1, hi - 1);
            int i = mid - 2, j = len2 - 1, dest = hi - 2;
            for (; ; )
            {
                for (int count = 0; count < MIN_GALLOP; )
                {
                    if (i < lo || j < 0)
                    {
                        goto outerBreak;
                    }
                    else if (CompareSaved(j, i) >= 0)
                    {
                        Restore(j--, dest--);
                        count = 0;
                    }
                    else
                    {
                        Copy(i--, dest--);
                        ++count;
                    }
                }
                // galloping
                int next = UpperSaved3(lo, i + 1, j);
                while (i >= next)
                {
                    Copy(i--, dest--);
                }
                Restore(j--, dest--);
            //outerContinue: ; // LUCENENET NOTE: Not referenced
            }
        outerBreak:
            for (; j >= 0; --dest)
            {
                Restore(j--, dest);
            }
            if (Debugging.AssertsEnabled) Debugging.Assert(i == dest);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual int LowerSaved(int from, int to, int val)
        {
            int len = to - from;
            while (len > 0)
            {
                int half = (int)((uint)len >> 1);
                int mid = from + half;
                if (CompareSaved(val, mid) > 0)
                {
                    from = mid + 1;
                    len = len - half - 1;
                }
                else
                {
                    len = half;
                }
            }
            return from;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual int UpperSaved(int from, int to, int val)
        {
            int len = to - from;
            while (len > 0)
            {
                int half = (int)((uint)len >> 1);
                int mid = from + half;
                if (CompareSaved(val, mid) < 0)
                {
                    len = half;
                }
                else
                {
                    from = mid + 1;
                    len = len - half - 1;
                }
            }
            return from;
        }

        // faster than lowerSaved when val is at the beginning of [from:to[
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual int LowerSaved3(int from, int to, int val)
        {
            int f = from, t = f + 1;
            while (t < to)
            {
                if (CompareSaved(val, t) <= 0)
                {
                    return LowerSaved(f, t, val);
                }
                int delta = t - f;
                f = t;
                t += delta << 1;
            }
            return LowerSaved(f, to, val);
        }

        //faster than upperSaved when val is at the end of [from:to[
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal virtual int UpperSaved3(int from, int to, int val)
        {
            int f = to - 1, t = to;
            while (f > from)
            {
                if (CompareSaved(val, f) >= 0)
                {
                    return UpperSaved(f, t, val);
                }
                int delta = t - f;
                t = f;
                f -= delta << 1;
            }
            return UpperSaved(from, t, val);
        }

        /// <summary>
        /// Copy data from slot <paramref name="src"/> to slot <paramref name="dest"/>>. </summary>
        protected abstract void Copy(int src, int dest);

        /// <summary>
        /// Save all elements between slots <paramref name="i"/> and <paramref name="i"/>+<paramref name="len"/>
        /// into the temporary storage.
        /// </summary>
        protected abstract void Save(int i, int len);

        /// <summary>
        /// Restore element <paramref name="j"/> from the temporary storage into slot <paramref name="i"/>. </summary>
        protected abstract void Restore(int i, int j);

        /// <summary>
        /// Compare element <paramref name="i"/> from the temporary storage with element
        /// <paramref name="j"/> from the slice to sort, similarly to
        /// <see cref="Sorter.Compare(int, int)"/>.
        /// </summary>
        protected abstract int CompareSaved(int i, int j);
    }
}