using System;

namespace ZB.MOM.WW.LmxProxy.Host.Domain
{
    /// <summary>
    ///     Value, Timestamp, and Quality structure for SCADA data.
    /// </summary>
    public readonly struct Vtq : IEquatable<Vtq>
    {
        /// <summary>
        ///     Gets the value.
        /// </summary>
        public object? Value { get; }

        /// <summary>
        ///     Gets the timestamp when the value was read.
        /// </summary>
        public DateTime Timestamp { get; }

        /// <summary>
        ///     Gets the quality of the value.
        /// </summary>
        public Quality Quality { get; }

        /// <summary>
        ///     Initializes a new instance of the <see cref="Vtq" /> struct.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="timestamp">The timestamp when the value was read.</param>
        /// <param name="quality">The quality of the value.</param>
        public Vtq(object? value, DateTime timestamp, Quality quality)
        {
            Value = value;
            Timestamp = timestamp;
            Quality = quality;
        }

        /// <summary>
        ///     Creates a new <see cref="Vtq" /> instance with the specified value and quality, using the current UTC timestamp.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="quality">The quality of the value.</param>
        /// <returns>A new <see cref="Vtq" /> instance.</returns>
        public static Vtq New(object value, Quality quality) => new(value, DateTime.UtcNow, quality);

        /// <summary>
        ///     Creates a new <see cref="Vtq" /> instance with the specified value, timestamp, and quality.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="timestamp">The timestamp when the value was read.</param>
        /// <param name="quality">The quality of the value.</param>
        /// <returns>A new <see cref="Vtq" /> instance.</returns>
        public static Vtq New(object value, DateTime timestamp, Quality quality) => new(value, timestamp, quality);

        /// <summary>
        ///     Creates a <see cref="Vtq" /> instance with good quality and the current UTC timestamp.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>A new <see cref="Vtq" /> instance with good quality.</returns>
        public static Vtq Good(object value) => new(value, DateTime.UtcNow, Quality.Good);

        /// <summary>
        ///     Creates a <see cref="Vtq" /> instance with bad quality and the current UTC timestamp.
        /// </summary>
        /// <param name="value">The value. Optional.</param>
        /// <returns>A new <see cref="Vtq" /> instance with bad quality.</returns>
        public static Vtq Bad(object? value = null) => new(value, DateTime.UtcNow, Quality.Bad);

        /// <summary>
        ///     Creates a <see cref="Vtq" /> instance with uncertain quality and the current UTC timestamp.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>A new <see cref="Vtq" /> instance with uncertain quality.</returns>
        public static Vtq Uncertain(object value) => new(value, DateTime.UtcNow, Quality.Uncertain);

        /// <summary>
        ///     Determines whether the specified <see cref="Vtq" /> is equal to the current <see cref="Vtq" />.
        /// </summary>
        /// <param name="other">The <see cref="Vtq" /> to compare with the current <see cref="Vtq" />.</param>
        /// <returns>true if the specified <see cref="Vtq" /> is equal to the current <see cref="Vtq" />; otherwise, false.</returns>
        public bool Equals(Vtq other) =>
            Equals(Value, other.Value) && Timestamp.Equals(other.Timestamp) && Quality == other.Quality;

        /// <summary>
        ///     Determines whether the specified object is equal to the current <see cref="Vtq" />.
        /// </summary>
        /// <param name="obj">The object to compare with the current <see cref="Vtq" />.</param>
        /// <returns>true if the specified object is equal to the current <see cref="Vtq" />; otherwise, false.</returns>
        public override bool Equals(object obj) => obj is Vtq other && Equals(other);

        /// <summary>
        ///     Returns the hash code for this instance.
        /// </summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        public override int GetHashCode()
        {
            unchecked
            {
                int hashCode = Value != null ? Value.GetHashCode() : 0;
                hashCode = (hashCode * 397) ^ Timestamp.GetHashCode();
                hashCode = (hashCode * 397) ^ (int)Quality;
                return hashCode;
            }
        }

        /// <summary>
        ///     Returns a string that represents the current object.
        /// </summary>
        /// <returns>A string that represents the current object.</returns>
        public override string ToString() =>
            $"{{Value={Value}, Timestamp={Timestamp:yyyy-MM-dd HH:mm:ss.fff}, Quality={Quality}}}";

        /// <summary>
        ///     Determines whether two specified instances of <see cref="Vtq" /> are equal.
        /// </summary>
        /// <param name="left">The first <see cref="Vtq" /> to compare.</param>
        /// <param name="right">The second <see cref="Vtq" /> to compare.</param>
        /// <returns>true if left and right are equal; otherwise, false.</returns>
        public static bool operator ==(Vtq left, Vtq right) => left.Equals(right);

        /// <summary>
        ///     Determines whether two specified instances of <see cref="Vtq" /> are not equal.
        /// </summary>
        /// <param name="left">The first <see cref="Vtq" /> to compare.</param>
        /// <param name="right">The second <see cref="Vtq" /> to compare.</param>
        /// <returns>true if left and right are not equal; otherwise, false.</returns>
        public static bool operator !=(Vtq left, Vtq right) => !left.Equals(right);
    }
}
