using System.Collections;
using System.Diagnostics;

namespace InnovEnergy.Lib.Utils;

public readonly struct Maybe<T> : IReadOnlyCollection<T>
{
    private T Value { get; }
    public Boolean HasValue { get; }

    internal Maybe(T value, Boolean hasValue)
    {
        HasValue = hasValue;
        Value = value;
    }

    public static Maybe<T> Nothing { get; } = new Maybe<T>(default!, false);

    public T IfNothing(T t) => HasValue ? Value : t;

    public Maybe<R> Select<R>(Func<T, R> map)
    {
        return HasValue ? map(Value) : Maybe<R>.Nothing;
    }

    public IEnumerator<T> GetEnumerator()
    {
        if (Value != null)
            yield return Value;
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    public static implicit operator Maybe<T>(T? t)
    {
        Debug.Assert(typeof(T).IsClass);
        return new Maybe<T>(t!, t != null);
    }

    public Maybe<T> Do(Action<T> action)
    {
        if (HasValue)
            action(Value);

        return this;
    }

    public Int32 Count => HasValue ? 1 : 0;

    public override String ToString() => HasValue ? Value!.ToString()! : "Nothing";
    public override Int32 GetHashCode() => HasValue ? Value!.GetHashCode() : 0;
    public override Boolean Equals(Object? obj) => obj is Maybe<T> other && Equals(other);

    public Boolean Equals(Maybe<T> other) => EqualityComparer<T>.Default.Equals(Value, other.Value) && HasValue == other.HasValue;
    public static Boolean operator ==(Maybe<T> left, Maybe<T> right) => left.Equals(right);
    public static Boolean operator !=(Maybe<T> left, Maybe<T> right) => !left.Equals(right);
}


public static class MaybeExtensions
{
    public static Maybe<T> Maybe<T>(this IEnumerable<T> ts) where T : class
    {
        if (ts is Maybe<T> mb)
            return mb;

        return ts.SingleOrDefault().Maybe();
    }

    public static Maybe<T> Maybe<T>(this T? t) where T : class
    {
        return new Maybe<T>(t!, t != null);
    }

    public static Maybe<T> Maybe<T>(this Nullable<T> t) where T: struct
    {
        return new Maybe<T>(t ?? default, t.HasValue);
    }
}


public static class MaybeEnumerableExtensions
{

    public static IEnumerable<T> Flatten<T>(this IEnumerable<Maybe<T>> src) => src.SelectMany(s => s);

    // public static Maybe<T> FirstMaybe<T>(this IEnumerable<T> t) where T: class
    // {
    //     using var enumerator = t.GetEnumerator();
    //
    //     return enumerator.MoveNext()
    //          ? enumerator.Current
    //          : null;
    // }
    //
    // public static Maybe<T> FirstMaybe<T>(this IEnumerable<T> t, Func<T, Boolean> predicate) where T: class
    // {
    //     return t.Where(predicate).FirstMaybe();
    // }
    //
    // public static Maybe<T> SingleMaybe<T>(this IEnumerable<T> t) where T: class
    // {
    //     using var enumerator = t.GetEnumerator();
    //
    //     if (!enumerator.MoveNext())
    //         return Maybe<T>.Nothing;
    //
    //     var single = enumerator.Current;
    //
    //     if (enumerator.MoveNext())
    //         throw new InvalidOperationException("More than a single element encountered");
    //
    //     return single.Maybe();
    // }
    //
    // public static Maybe<T> SingleMaybe<T>(this IEnumerable<T> t, Func<T, Boolean> predicate) where T: class
    // {
    //     return t.Where(predicate).SingleMaybe();
    // }
    //
    // public static Maybe<T> FirstMaybe<T>(this IEnumerable<Nullable<T>> ts) where T: struct
    // {
    //     using var enumerator = ts.GetEnumerator();
    //
    //     return enumerator.MoveNext() && enumerator.Current.HasValue
    //          ? enumerator.Current.Value
    //          : Maybe<T>.Nothing;
    // }
    //
    // public static Maybe<T> FirstMaybe<T>(this IEnumerable<Nullable<T>> ts, Func<T?, Boolean> predicate) where T: struct
    // {
    //     return ts.Where(predicate).FirstMaybe();
    // }
    //
    // public static Maybe<T> SingleMaybe<T>(this IEnumerable<Nullable<T>> ts) where T: struct
    // {
    //     using var enumerator = ts.GetEnumerator();
    //
    //     if (!enumerator.MoveNext())
    //         return Maybe<T>.Nothing;
    //
    //     var single = enumerator.Current;
    //
    //     if (enumerator.MoveNext())
    //         throw new InvalidOperationException("More than a single element encountered");
    //
    //     return single.Maybe();
    // }
    //
    // public static Maybe<T> SingleMaybe<T>(this IEnumerable<Nullable<T>> ts, Func<T?, Boolean> predicate) where T: struct
    // {
    //     return ts.Where(predicate).SingleMaybe();
    // }
}