using System;

namespace KitsuneCafe.System
{
    public class Option
    {
        public static IOption<T> Some<T>(T value) => IOption<T>.Some(value);
        public static IOption<T> None<T>() => IOption<T>.None();
    }

    public interface IOption<T>
    {
        private static IOption<T> none = new None<T>();

        public abstract bool IsSome { get; }

        public static IOption<T> Some(T value) => new Some<T>(value);
        public static IOption<T> None() => none;

        public abstract T Unwrap();

        public abstract IOption<TResult> SelectMany<TResult>(Func<T, IOption<TResult>> func);
        public abstract IOption<TResult> Select<TResult>(Func<T, TResult> func);
        public abstract IOption<T> Where(Func<T, bool> predicate);
        public abstract TResult Match<TResult>(Func<T, TResult> ifSome, Func<TResult> ifNone);
        public abstract T UnwrapOrDefault(T defaultValue = default);
        public abstract T UnwrapOrElse(Func<T> func);

        public virtual IOption<T> Do(Action<T> func)
        {
            if (IsSome) { func(Unwrap()); }
            return this;
        }
    }

    public readonly struct Some<T> : IOption<T>
    {
        private readonly T value;

        public readonly bool IsSome => true;

        internal Some(T value)
        {
            this.value = value;
        }

        public IOption<TResult> SelectMany<TResult>(Func<T, IOption<TResult>> func)
        {
            return func(value);
        }

        public T Unwrap()
        {
            return value;
        }

        public IOption<TResult> Select<TResult>(Func<T, TResult> func)
        {
            return IOption<TResult>.Some(func(value));
        }

        public IOption<T> Where(Func<T, bool> predicate)
        {
            if (predicate(value))
            {
                return this;
            }

            return IOption<T>.None();
        }

        public TResult Match<TResult>(Func<T, TResult> ifSome, Func<TResult> ifNone)
        {
            return ifSome(value);
        }

        public T UnwrapOrDefault(T defaultValue = default)
        {
            return value;
        }

        public T UnwrapOrElse(Func<T> _func)
        {
            return value;
        }
    }

    public readonly struct None<T> : IOption<T>
    {
        public readonly bool IsSome => false;

        public IOption<TResult> SelectMany<TResult>(Func<T, IOption<TResult>> func)
        {
            return IOption<TResult>.None();
        }

        public T Unwrap()
        {
            throw new InvalidOperationException("Attempted to unwrap value of None.");
        }

        public IOption<TResult> Select<TResult>(Func<T, TResult> func)
        {
            return IOption<TResult>.None();
        }

        public IOption<T> Where(Func<T, bool> predicate)
        {
            return IOption<T>.None();
        }

        public TResult Match<TResult>(Func<T, TResult> ifSome, Func<TResult> ifNone)
        {
            return ifNone();
        }

        public T UnwrapOrDefault(T defaultValue = default)
        {
            return defaultValue;
        }

        public T UnwrapOrElse(Func<T> func)
        {
            return func();
        }
    }
}

