// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Diagnostics;
using System.Reflection.Metadata;

namespace Microsoft.Cci
{
    /// <summary>
    /// A region representing an exception handler clause. The region exposes the type (catch or
    /// finally) and the bounds of the try block and catch or finally block as needed by 
    /// </summary>
    internal abstract class ExceptionHandlerRegion
    {
        /// <summary>
        /// Label instruction corresponding to the start of try block
        /// </summary>
        public int TryStartOffset { get; }

        /// <summary>
        /// Label instruction corresponding to the end of try block
        /// </summary>
        public int TryEndOffset { get; }

        /// <summary>
        /// Label instruction corresponding to the start of handler block
        /// </summary>
        public int HandlerStartOffset { get; }

        /// <summary>
        /// Label instruction corresponding to the end of handler block
        /// </summary>
        public int HandlerEndOffset { get; }

        public ExceptionHandlerRegion(
            int tryStartOffset,
            int tryEndOffset,
            int handlerStartOffset,
            int handlerEndOffset)
        {
            Debug.Assert(tryStartOffset < tryEndOffset);
            Debug.Assert(tryEndOffset <= handlerStartOffset);
            Debug.Assert(handlerStartOffset < handlerEndOffset);
            Debug.Assert(tryStartOffset >= 0);
            Debug.Assert(tryEndOffset >= 0);
            Debug.Assert(handlerStartOffset >= 0);
            Debug.Assert(handlerEndOffset >= 0);

            TryStartOffset = tryStartOffset;
            TryEndOffset = tryEndOffset;
            HandlerStartOffset = handlerStartOffset;
            HandlerEndOffset = handlerEndOffset;
        }

        public int HandlerLength => HandlerEndOffset - HandlerStartOffset;
        public int TryLength => TryEndOffset - TryStartOffset;

        /// <summary>
        /// Handler kind for this SEH info
        /// </summary>
        public abstract ExceptionRegionKind HandlerKind { get; }

        /// <summary>
        /// If HandlerKind == HandlerKind.Catch, this is the type of exception to catch. If HandlerKind == HandlerKind.Filter, this is System.Object.
        /// Otherwise this is a Dummy.TypeReference.
        /// </summary>
        public virtual ITypeReference? ExceptionType
        {
            get
            {
                return null;
            }
        }

        /// <summary>
        /// Label instruction corresponding to the start of filter decision block
        /// </summary>
        public virtual int FilterDecisionStartOffset
        {
            get { return 0; }
        }
    }

    internal sealed class ExceptionHandlerRegionFinally : ExceptionHandlerRegion
    {
        public ExceptionHandlerRegionFinally(
            int tryStartOffset,
            int tryEndOffset,
            int handlerStartOffset,
            int handlerEndOffset)
            : base(tryStartOffset, tryEndOffset, handlerStartOffset, handlerEndOffset)
        {
        }

        public override ExceptionRegionKind HandlerKind
        {
            get { return ExceptionRegionKind.Finally; }
        }
    }

    internal sealed class ExceptionHandlerRegionFault : ExceptionHandlerRegion
    {
        public ExceptionHandlerRegionFault(
            int tryStartOffset,
            int tryEndOffset,
            int handlerStartOffset,
            int handlerEndOffset)
            : base(tryStartOffset, tryEndOffset, handlerStartOffset, handlerEndOffset)
        {
        }

        public override ExceptionRegionKind HandlerKind
        {
            get { return ExceptionRegionKind.Fault; }
        }
    }

    internal sealed class ExceptionHandlerRegionCatch : ExceptionHandlerRegion
    {
        private readonly ITypeReference _exceptionType;

        public ExceptionHandlerRegionCatch(
            int tryStartOffset,
            int tryEndOffset,
            int handlerStartOffset,
            int handlerEndOffset,
            ITypeReference exceptionType)
            : base(tryStartOffset, tryEndOffset, handlerStartOffset, handlerEndOffset)
        {
            _exceptionType = exceptionType;
        }

        public override ExceptionRegionKind HandlerKind
        {
            get { return ExceptionRegionKind.Catch; }
        }

        public override ITypeReference ExceptionType
        {
            get { return _exceptionType; }
        }
    }

    internal sealed class ExceptionHandlerRegionFilter : ExceptionHandlerRegion
    {
        private readonly int _filterDecisionStartOffset;

        public ExceptionHandlerRegionFilter(
            int tryStartOffset,
            int tryEndOffset,
            int handlerStartOffset,
            int handlerEndOffset,
            int filterDecisionStartOffset)
            : base(tryStartOffset, tryEndOffset, handlerStartOffset, handlerEndOffset)
        {
            Debug.Assert(filterDecisionStartOffset >= 0);

            _filterDecisionStartOffset = filterDecisionStartOffset;
        }

        public override ExceptionRegionKind HandlerKind
        {
            get { return ExceptionRegionKind.Filter; }
        }

        public override int FilterDecisionStartOffset
        {
            get { return _filterDecisionStartOffset; }
        }
    }
}
