using System;
using System.Collections.Generic;
using System.Linq;

namespace Hiperscan.Common
{
    public class HSException : Exception
    {
        const int DEFAULT_EXIT_CODE = 1;

        public delegate void OnExitDelegate();
        protected readonly OnExitDelegate on_exit;

        public readonly int ExitCode;

        public HSException(string message, OnExitDelegate onExit, int exitCode = DEFAULT_EXIT_CODE) : base(message)
        {
            this.on_exit = onExit;
            this.ExitCode = exitCode;
        }
        public HSException(string message, int exitCode = DEFAULT_EXIT_CODE) : this(message, () => {}, exitCode) {}

        public HSException(string message, Exception inner, OnExitDelegate onExit, int exitCode = DEFAULT_EXIT_CODE) : base(message, inner)
        {
            this.on_exit = onExit;
            this.ExitCode = exitCode;
        }
        public HSException(string message, Exception inner, int exitCode = DEFAULT_EXIT_CODE) : this(message, inner, () => {}, exitCode) {}

        /// <summary>Call either this or <see cref="OnExit(Exception)"/>, <see cref="OnExit(HSException)"/> at the catch site to process
        /// all <see cref="on_exit"/>s in the chain of exceptions linked by <see cref="Exception.InnerException"/>,
        /// starting at the passed <c>Exception</c> and continuing to a leaf.</summary>
        public void OnExit()
        {
            var all_handles = this.WalkChain()
                                  .OfType<HSException>()
                                  .Select(ex => ex.on_exit)
                                  .Aggregate((all, next) => all + next);
            all_handles();
        }

        /// <summary>Call either this or <see cref="OnExit()"/>, <see cref="OnExit(Exception)"/> at the catch site to process
        /// all <see cref="on_exit"/>s in the chain of exceptions linked by <see cref="Exception.InnerException"/>,
        /// starting at the passed <c>Exception</c> and continuing to a leaf.</summary>
        public static void OnExit(HSException exception)
        {
            exception.OnExit();
        }

        /// <summary>Call either this or <see cref="OnExit()"/>, <see cref="OnExit(HSException)"/> at the catch site to process
        /// all <see cref="on_exit"/>s in the chain of exceptions linked by <see cref="Exception.InnerException"/>,
        /// starting at the passed <c>Exception</c> and continuing to a leaf.</summary>
        public static void OnExit(Exception exception)
        {
            exception.WalkChain()
                     .OfType<HSException>()
                     .FirstOrDefault()
                     ?.OnExit();
        }
    }

    public static class Exceptions
    {
        public static IEnumerable<Exception> WalkChain(this Exception exception)
        {
            while (exception != null)
            {
                yield return exception;
                exception = exception.InnerException;
            }
        }
    }
}
