using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using System.Runtime.CompilerServices;

namespace Burst.Compiler.IL.Tests
{
    internal class ControlFlowsTryCatchFinally
    {
        [TestCompiler(-10)]
        [TestCompiler(0)]
        [TestCompiler(10)]
        public static int TryFinallySimple(int i)
        {
            try
            {
                if (i == 0) // case 0
                {
                    return 1;
                }
                else if (i > 0) // case 10
                {
                    i = i * 2;
                }
                else
                {
                    i = i * 3; // case -10
                }
            }
            finally
            {
                i = i + 1;
            }

            return i; // both case 10 and -10
        }

        static void Oof()
        {
        }

        [TestCompiler]
        public static void TryFinallyFirstBlock()
        {
            try
            {
            }
            finally
            {
                Oof();
            }
        }

        static int MagicA(int b, int f, int h, CustomBuffer s)
        {
            return b+s.Hash()+f-h;
        }
    
        static bool MagicB(int c,out int t)
        {
            t = 0;
            if (c>10)
            {
                t = c;
                return true;
            }
            return false;
        }

        // This test catches an issue with the de-stackifier. (see ILBuilder.cs:1254 (flushStack))
        // Needs to be unoptimised to trigger
        [TestCompiler(0)]
        [TestCompiler(99)]
        [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
        public static int TryUnbalancedFinally(int i)
        {
            // this if is required to force the destackifier to process the final block, before processing the block the contains the endfinally
            if (i == 99)
            {
                return default;
            }

            int resultB = i;

            using var buffer = new CustomBuffer(32);

            return resultB + MagicA(i,
               MagicB(i*2, out var r) ? r : default,
               MagicB(i, out var t) ? t : default,
               buffer);
        }


        [TestCompiler(-3)]
        [TestCompiler(0)]
        [TestCompiler(3)]
        public static int TryFinallyComplex1(int i)
        {
            try
            {
                try
                {
                    if (i == 0)
                    {
                        return i - 1;
                    }

                    i += 3;
                }
                finally
                {
                    if (i == 0) // case i: -3
                    {
                        i = 1;
                    }
                    else
                    {
                        i = i * 10; // case i: 3
                    }
                }
            }
            finally
            {
                i = i * 2; // both -3 and 3
            }

            return i + 1;
        }

        [TestCompiler(-10)]
        [TestCompiler(0)] // case 0
        [TestCompiler(10)]
        public static int TryFinallyComplex2(int i)
        {
            // First block of nested try/catch
            try
            {
                try
                {
                    if (i == 0) // case 0
                    {
                        return i - 1;
                    }

                    i = i * 2;
                }
                finally
                {
                    i++;
                }
            }
            finally
            {
                i = i * 3;
            }

            // Second block of nested try/catch
            try
            {
                i = i - 2;

                try
                {
                    if (i < 0) // case -10
                    {
                        return i * 5;
                    }

                    i += 3; // case 10
                }
                finally
                {
                    i += 11;
                }
            }
            finally
            {
                i = i * 3;
            }

            return i + 1; // case 10
        }


        [TestCompiler(0)]
        [TestCompiler(1)]
        [TestCompiler(10)]
        [TestCompiler(20)]
        public static int TryFinallyComplex3(int x)
        {
            bool k = true;
            int num = 0;
            try
            {
                while (k)
                {
                    if (x < 10)
                    {
                        num |= 2;
                        try
                        {
                            if (x == 1) return num;
                        }
                        finally
                        {
                            k = false;
                        }

                        continue;
                    }

                    num |= 1;
                    try
                    {
                        if (x == 20) return num;
                    }
                    finally
                    {
                        k = false;
                    }
                }
            }
            finally
            {
                num |= 4;
            }

            return num;
        }

        [TestCompiler]
        public static int TryUsingDispose()
        {
            using (var buffer = new CustomBuffer(32))
            {
                return buffer.Hash();
            }
        }

        [TestCompiler]
        public static int ForEachTryFinally()
        {
            int hashCode = 0;
            foreach (var value in new RangeEnumerable(1, 100))
            {
                hashCode = (hashCode * 397) ^ value;
            }
            return hashCode;
        }

        [TestCompiler(ExpectCompilerException = true, ExpectedDiagnosticId = DiagnosticId.ERR_CatchConstructionNotSupported)]
        public static int TryCatch()
        {
            try
            {
                return default(int);
            }
            catch (InvalidOperationException)
            {
                return 1;
            }
        }

        private unsafe struct CustomBuffer : IDisposable
        {
            private readonly int _size;
            private byte* _buffer;

            public CustomBuffer(int size)
            {
                _size = size;
                _buffer = (byte*)UnsafeUtility.Malloc(size, 4, Allocator.Persistent);
                for (int i = 0; i < size; i++)
                {
                    _buffer[i] = (byte)(i + 1);
                }
            }

            public int Hash()
            {
                int hashCode = _size;
                for (int i = 0; i < _size; i++)
                {
                    hashCode = (hashCode * 397) ^ (byte)_buffer[i];
                }
                return hashCode;
            }

            public unsafe void Dispose()
            {
                if (_buffer != null)
                {
                    UnsafeUtility.Free(_buffer, Allocator.Persistent);
                    _buffer = (byte*) 0;
                }
            }
        }

        private struct RangeEnumerable : IEnumerable<int>
        {
            private readonly int _from;
            private readonly int _to;

            public RangeEnumerable(int from, int to)
            {
                _from = @from;
                _to = to;
            }

            public Enumerator GetEnumerator()
            {
                return new Enumerator();
            }

            IEnumerator<int> IEnumerable<int>.GetEnumerator()
            {
                return GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            public struct Enumerator : IEnumerator<int>
            {
                private readonly int _from;
                private readonly int _to;

                public Enumerator(int from, int to)
                {
                    _from = @from;
                    _to = to;
                    Current = -1;
                }

                public void Dispose()
                {
                    // nothing to do
                }

                public bool MoveNext()
                {
                    if (Current < 0)
                    {
                        Current = _from;
                        return true;
                    }

                    int nextIndex = Current + 1;
                    if (nextIndex >= _from && nextIndex <= _to)
                    {
                        Current = nextIndex;
                        return true;
                    }
                    return false;
                }

                public void Reset()
                {
                }

                public int Current { get; private set; }

                object IEnumerator.Current => Current;
            }
        }
    }
}