using System.Diagnostics.CodeAnalysis;
using Voile.Resources;

namespace Voile
{
    /// <summary>
    /// Wraps a reference to an asset of a given type.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public sealed class ResourceRef<T> where T : Resource
    {
        /// <summary>
        /// Resource GUID this ResourceRef maps to.
        /// </summary>
        public readonly Guid Guid = Guid.Empty;
        public bool HasValue => Guid != Guid.Empty;

        /// <summary>
        /// Retrieves a <see cref="Resource"/>.<br />
        /// This will throw an <see cref="InvalidOperationException"/> if the resource wasn't loaded or is invalid. <br />
        /// You can check if resource was loaded with <see cref="HasValue"/>, or consider using <see cref="TryGetValue"/>.
        /// </summary> 
        public T Value => ResourceManager.GetResource<T>(Guid)
            ?? throw new InvalidOperationException($"Resource with GUID {Guid} is not loaded or invalid.");

        /// <summary>
        /// Retrieves a resource or <c>null</c> if the resource wasn't loaded or is invalid.
        /// </summary>
        public T? ValueOrNull => ResourceManager.GetResource<T>(Guid);

        /// <summary>
        /// Tries to retrieve a <see cref="Resource"/>.
        /// </summary>
        /// <param name="value">An instance of a retrieved <see cref="Resource"/>.</param>
        /// <returns><c>true</c> if the resource was successfully retrieved, otherwise <c>false</c>.</returns>
        public bool TryGetValue([NotNullWhen(true)] out T? value)
        {
            value = ResourceManager.GetResource<T>(Guid);
            return value != null;
        }

        /// <summary>
        /// Create an empty <see cref="ResourceRef"/>.
        /// </summary>
        /// <returns></returns>
        public static ResourceRef<T> Empty()
        {
            return new ResourceRef<T>(Guid.Empty);
        }

        public ResourceRef(Guid guid)
        {
            Guid = guid;
        }

        public override bool Equals(object? obj)
        {
            return obj is ResourceRef<T> other && Guid.Equals(other.Guid);
        }

        public override int GetHashCode()
        {
            return Guid.GetHashCode();
        }

        public static bool operator ==(ResourceRef<T>? left, ResourceRef<T>? right)
        {
            if (ReferenceEquals(left, right)) return true;
            if (left is null || right is null) return false;
            return left.Guid == right.Guid;
        }

        public static bool operator !=(ResourceRef<T>? left, ResourceRef<T>? right)
        {
            return !(left == right);
        }
    }

    /// <summary>
    /// Represents data usable by Voile.
    /// </summary>
    public abstract class Resource
    {
        /// <summary>
        /// Path to this resource.
        /// </summary>
        public string Path { get; private set; } = string.Empty;

        public Resource(string path)
        {
            Path = path;
        }
    }

    /// <summary>
    /// Represents a Resource that requires systems to react to its changes.
    /// </summary>
    public interface IUpdatableResource
    {
        /// <summary>
        /// Gets a value indicating whether this resource's state has changed and needs to be reloaded.
        /// </summary>
        bool Dirty { get; }

        /// <summary>
        /// Marks this resource as updated.
        /// </summary>
        void MarkUpdated();
    }
}