using System;
using System.Collections.Generic;
using System.Linq;
using Quadarax.Foundation.Core.Attributes;
using Quadarax.Foundation.Core.Value.Extensions;

namespace Quadarax.Foundation.Core.Exceptions
{
    /// <summary>
    /// Provides a base class for exceptions that include a code.
    /// </summary>
    [Serializable]
    public class CodeException : Exception
    {
        public int Code => this.GetCode();
        public CodeException(int code, string message, Exception innerException) : base(message, innerException)
        {
            this.SetCode(code);
        }

        public CodeException(int code, string message) : base(message)
        {
            this.SetCode(code);
        }
       
        public CodeException(int code, string message, params object[] messageArguments) : base(string.Format(message, messageArguments))
        {
            this.SetCode(code);
        }
        public CodeException(int code, string message, Exception innerException, params object[] messageArguments) : base(string.Format(message, messageArguments), innerException)
        {
            this.SetCode(code);
        }

    }

    /// <summary>
    /// Provides a base class for exceptions that include a code in enumeration form.
    /// Exception message is resolved from the <see cref="ErrorMessageAttribute"/> of the enumeration.
    /// </summary>
    /// <typeparam name="TCodeEnum">Enumeration that defines error codes end its message defined by <see cref="ErrorMessageAttribute"/>.</typeparam>
    public class CodeException<TCodeEnum> : CodeException where TCodeEnum : struct, Enum
    {

        public new TCodeEnum Code => (TCodeEnum)Enum.ToObject(typeof(TCodeEnum), this.GetCode());

        /// <summary>
        /// Static cache for error messages.
        /// </summary>
        private static readonly IDictionary<int, string> _messageCache = new Dictionary<int, string>();

        public CodeException(TCodeEnum code, Exception innerException) : base(Convert.ToInt32(code),
            ResolveMessage(code), innerException)
        {
        }

        public CodeException(TCodeEnum code) : base(Convert.ToInt32(code), ResolveMessage(code))
        {
        }

        public CodeException(TCodeEnum code, params object[] messageArguments) : base(
            Convert.ToInt32(code), ResolveMessage(code, messageArguments))
        {
        }

        public CodeException(TCodeEnum code, Exception innerException, params object[] messageArguments)
            : base(Convert.ToInt32(code),ResolveMessage(code,messageArguments), innerException)
        {
        }

        private static string ResolveMessage(TCodeEnum code, params object[] messageArguments)
        {
            var codeValue = Convert.ToInt32(code);
            // check if cache is initialized
            if (_messageCache.Count == 0)
            {
                // initialize cache
                var fields = typeof(TCodeEnum).GetFields();
                var names = Enum.GetNames(typeof(TCodeEnum)).ToArray();
                var values = Enum.GetValues(typeof(TCodeEnum));
                // iterate through all fields of the enumeration
                foreach (var field in fields)
                {
                    var attr = field.GetCustomAttributes(typeof(ErrorMessageAttribute), false);
                    var index = Array.IndexOf(names, field.Name);
                    if (index<0) continue;
                    var fieldVal = (int) (values.GetValue(index) ?? -1);
                    // if value has attribute ErrorMessageAttribute, add it to the cache
                    if (attr.Length == 1)
                    {
                        var message = ((ErrorMessageAttribute)attr[0]).ErrorMessage;
                        _messageCache.Add(fieldVal, message);
                    }
                    else
                    {
                        // message not found, log warning and add default message to the cache
                        System.Diagnostics.Debug.WriteLine($"Error message in enumeration '{typeof(TCodeEnum).Name}' not found for code '{codeValue}'", "WARN");
                        _messageCache.Add(fieldVal, $"Error message code: {code} [{fieldVal}]");
                    }
                }
            }
            // return message from cache
            return string.Format(_messageCache[codeValue], messageArguments);
        }

    }
     
}
