using System;

namespace EscapeRoomEngine.Engine.Runtime.Utilities
{
    /// <summary>
    /// Optional type with a subset of the functionality of the one described in the Rust documentation at https://doc.rust-lang.org/std/option/enum.Option.html.
    /// </summary>
    public interface IOption<T>
    {
        public bool IsSome();
        public bool IsNone();
        public T Expect(string message);
        public T Unwrap();
        public T UnwrapOr(T def);
        public T UnwrapOrElse(Func<T> f);
        public IOption<T> And(IOption<T> other);
        public IOption<T> Or(IOption<T> other);
        public bool Contains(T value);
        public T Match(Func<T, T> some, Func<T> none);
        public void Match(Action<T> some, Action none);
        public void Match(Action<T> some);
        public void Match(Action none);
    }

    public class Some<T> : IOption<T>
    {
        private readonly T _value;
        
        private Some(T value) => _value = value;

        public bool IsSome() => true;
        public bool IsNone() => false;
        public T Expect(string message) => _value;
        public T Unwrap() => _value;
        public T UnwrapOr(T def) => _value;
        public T UnwrapOrElse(Func<T> f) => _value;
        public IOption<T> And(IOption<T> other) => other;
        public IOption<T> Or(IOption<T> other) => this;
        public bool Contains(T value) => _value.Equals(value);
        public T Match(Func<T, T> some, Func<T> none) => some(_value);
        public void Match(Action<T> some, Action none) => some(_value);
        public void Match(Action<T> some) => some(_value);
        public void Match(Action none) {}

        public static IOption<T> Of(T value) => new Some<T>(value);
    }

    public class None<T> : IOption<T>
    {
        public bool IsSome() => false;
        public bool IsNone() => true;
        public T Expect(string message) => throw new OptionException(message);
        public T Unwrap() => throw new OptionException("Tried to unwrap None.");
        public T UnwrapOr(T def) => def;
        public T UnwrapOrElse(Func<T> f) => f();
        public IOption<T> And(IOption<T> other) => this;
        public IOption<T> Or(IOption<T> other) => other;
        public bool Contains(T value) => false;
        public T Match(Func<T, T> some, Func<T> none) => none();
        public void Match(Action<T> some, Action none) => none();
        public void Match(Action<T> some) {}
        public void Match(Action none) => none();

        public static IOption<T> New() => new None<T>();
    }
}