using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using PerformanceCounters;
using PostSharp.Aspects;
using PostSharp.Extensibility;

namespace Aspects
{
    /// <summary>
    ///     Aspect which is measuring the average execution time in milliseconds for specific member call
    /// </summary>
    [Serializable]
    [AverageTimePerformanceCounterAspect(AttributeExclude = true)]
    public sealed class AverageTimePerformanceCounterAspectAttribute : OnMethodBoundaryAspect
    {
        private static readonly Stopwatch Stopwatch = new Stopwatch();
        private readonly string _categoryName;
        private readonly string _categoryHelpText;
        private readonly string _counterName;
        private readonly string _performanceCounterHelperText;
        private string _instanceName;

        [NonSerialized]
        private IPerformanceCounter _performanceCounter;

        [NonSerialized]
        private bool _isEnabled;

        [NonSerialized]
        private IPerformanceCountersAspectsSettingsManager _performanceCounterSettingsManager;

        /// <summary>
        ///     Initializes the <see cref="AverageTimePerformanceCounterAspectAttribute" /> class.
        /// </summary>
        static AverageTimePerformanceCounterAspectAttribute()
        {
            Stopwatch.Start();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AverageTimePerformanceCounterAspectAttribute" /> class.
        /// </summary>
        /// <param name="counterName">Name of the counter.</param>
        /// <param name="categoryName">Name of the category.</param>
        /// <param name="categoryHelpText">The category help text.</param>
        /// <param name="performanceCounterHelperText">The performance counter helper text.</param>
        /// <exception cref="System.ArgumentNullException">categoryName
        /// or
        /// counterName</exception>
        public AverageTimePerformanceCounterAspectAttribute(string counterName = "average execution time for member call", string categoryName = "_Custom Category",
            string categoryHelpText = "", string performanceCounterHelperText = "Measure average execution time in milliseconds for specific member instance")
        {
            if (categoryName == null)
            {
                throw new ArgumentNullException("categoryName");
            }

            if (counterName == null)
            {
                throw new ArgumentNullException("counterName");
            }

            if (categoryHelpText == null)
            {
                throw new ArgumentNullException("categoryHelpText");
            }

            if (performanceCounterHelperText == null)
            {
                throw new ArgumentNullException("performanceCounterHelperText");
            }
            _categoryName = categoryName;
            _categoryHelpText = categoryHelpText;
            _counterName = counterName;
            _performanceCounterHelperText = performanceCounterHelperText;
        }

        /// <summary>
        ///     Method invoked at build time to initialize the instance fields of the current aspect. This method is invoked
        ///     before any other build-time method.
        /// </summary>
        /// <param name="method">Method to which the current aspect is applied</param>
        /// <param name="aspectInfo">Reserved for future usage.</param>
        public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo)
        {
            _instanceName = method.DeclaringType.FullName + "." + method.Name;
        }

        public override void OnEntry(MethodExecutionArgs args)
        {
            try
            {
                if (!_isEnabled)
                {
                    return;
                }

                args.MethodExecutionTag = Stopwatch.ElapsedMilliseconds;
                base.OnEntry(args);
            }
            catch (Exception e)
            {
                Trace.TraceError(e.ToString());
            }
        }

        public override void OnExit(MethodExecutionArgs args)
        {
            try
            {
                //MethodExecutionTag is null when the OnEntry was no executed. This can be the case when you change the configuration at runtime.
                if (!_isEnabled || args.MethodExecutionTag == null)
                {
                    return;
                }

                long time = Stopwatch.ElapsedMilliseconds - (long) args.MethodExecutionTag;
                _performanceCounter.Increment(time);
                base.OnExit(args);
            }
            catch (Exception e)
            {
                Trace.TraceError(e.ToString());
            }
        }

        public override void RuntimeInitialize(MethodBase method)
        {
            if (_performanceCounterSettingsManager == null)
            {
                _performanceCounterSettingsManager = PerformanceCountersAspectsServiceLocator.PerformanceCountersAspectsSettingsManager;

                if (PerformanceCountersAspectsServiceLocator.PerformanceCountersAspectsSettingsManager == null)
                {
                    throw new InvalidAnnotationException(string.Format("To allow to use '{0}' aspect you must implement concrete type of '{1}' and assign them to the '{2}'!", typeof (AverageTimePerformanceCounterAspectAttribute),
                        typeof (IPerformanceCountersAspectsSettingsManager), typeof (PerformanceCountersAspectsServiceLocator)));
                }
                _performanceCounterSettingsManager.ConfigurationChanged += (sender, args) => ConfigurationChanged();
            }

            _isEnabled = _performanceCounterSettingsManager.MembersToMeasured.Any(memberName => memberName == _instanceName);

            if (_isEnabled)
            {
                _performanceCounter = new PerformanceCounterAverageTimer32(_categoryName, _counterName, _instanceName, _categoryHelpText, _performanceCounterHelperText);
            }
        }

        private void ConfigurationChanged()
        {
            try
            {
                _isEnabled = _performanceCounterSettingsManager.MembersToMeasured.Any(memberName => memberName == _instanceName);

                if (_isEnabled)
                {
                    _performanceCounter = new PerformanceCounterAverageTimer32(_categoryName, _counterName, _instanceName, _categoryHelpText, _performanceCounterHelperText);
                }
                else
                {
                    if (_performanceCounter != null)
                    {
                        _performanceCounter.RemoveInstance();
                        _performanceCounter = null;
                    }
                }
            }
            catch (Exception e)
            {
                Trace.TraceError(e.ToString());
            }
        }
    }
}