// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace System.Diagnostics.Metrics
{
    /// <summary>
    /// ObservableInstrument{T} is the base class from which all metrics observable instruments will inherit from.
    /// </summary>
    /// <remarks>
    /// This class supports only the following generic parameter types: <see cref="byte" />, <see cref="short" />, <see cref="int" />, <see cref="long" />, <see cref="float" />, <see cref="double" />, and <see cref="decimal" />
    /// </remarks>
    public abstract class ObservableInstrument<T> : Instrument where T : struct
    {
        /// <summary>
        /// Create the metrics observable instrument using the properties meter, name, description, and unit.
        /// All classes extending ObservableInstrument{T} need to call this constructor when constructing object of the extended class.
        /// </summary>
        /// <param name="meter">The meter that created the instrument.</param>
        /// <param name="name">The instrument name. cannot be null.</param>
        /// <param name="unit">Optional instrument unit of measurements.</param>
        /// <param name="description">Optional instrument description.</param>
        protected ObservableInstrument(Meter meter, string name, string? unit, string? description) : this(meter, name, unit, description, tags: null)
        {
        }

        /// <summary>
        /// Create the metrics observable instrument using the properties meter, name, description, and unit.
        /// All classes extending ObservableInstrument{T} need to call this constructor when constructing object of the extended class.
        /// </summary>
        /// <param name="meter">The meter that created the instrument.</param>
        /// <param name="name">The instrument name. cannot be null.</param>
        /// <param name="unit">Optional instrument unit of measurements.</param>
        /// <param name="description">Optional instrument description.</param>
        /// <param name="tags">tags to attach to the counter.</param>
        protected ObservableInstrument(Meter meter, string name, string? unit, string? description, IEnumerable<KeyValuePair<string, object?>>? tags) : base(meter, name, unit, description, tags)
        {
            ValidateTypeParameter<T>();
        }

        /// <summary>
        /// Observe() fetches the current measurements being tracked by this instrument. All classes extending ObservableInstrument{T} need to implement this method.
        /// </summary>
        protected abstract IEnumerable<Measurement<T>> Observe();

        /// <summary>
        /// A property tells if the instrument is an observable instrument. This property will return true for all metrics observable instruments.
        /// </summary>
        public override bool IsObservable => true;

        // Will be called from MeterListener.RecordObservableInstruments for each observable instrument.
        internal override void Observe(MeterListener listener)
        {
            object? state = GetSubscriptionState(listener);

            IEnumerable<Measurement<T>> measurements = Observe();
            if (measurements is null)
            {
                return;
            }

            foreach (Measurement<T> measurement in measurements)
            {
                listener.NotifyMeasurement(this, measurement.Value, measurement.Tags, state);
            }
        }

        // Will be called from the concrete classes which extends ObservabilityInstrument<T> when calling Observe() method.
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static IEnumerable<Measurement<T>> Observe(object callback)
        {
            if (callback is Func<T> valueOnlyFunc)
            {
                return new Measurement<T>[1] { new Measurement<T>(valueOnlyFunc()) };
            }

            if (callback is Func<Measurement<T>> measurementOnlyFunc)
            {
                return new Measurement<T>[1] { measurementOnlyFunc() };
            }

            if (callback is Func<IEnumerable<Measurement<T>>> listOfMeasurementsFunc)
            {
                return listOfMeasurementsFunc();
            }

            Debug.Assert(false, "Execution shouldn't reach this point");
            return null;
        }

    }
}
