// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

part of 'either_base.dart';

/// {@template option_exception}
/// [OptionException] is sometimes threw by [Option] objects.
///
/// ```dart
/// throw OptionException('Emergency failure!');
/// ```
/// {@endtemplate}
class OptionException extends _EitherBaseException {
  /// {@macro result_exception}
  const OptionException(super.message);

  @override
  String toString() => 'OptionException: $message';
}

/// {@template option}
/// [Option] is a container object which may or may not contain a non-null 
/// value. If a [Value] is present, isPresent() will return true and get() 
/// will return the value.
/// 
/// Additional methods that depend on the presence or absence of 
/// a contained value are provided, such as orElse() (return a default value 
/// if value not present) and ifPresent() (execute a block of code if the 
/// value is present).
/// {@endtemplate}
abstract class Option<T> extends _EitherBase<T, void> {
  /// {@macro option}
  const Option._();

  /// Represents the left side of [Option] class.
  bool get isPresent => _isLeft;

  /// Represents the right side of [Option] class.
  bool get isNull => _isRight;

  /// Get nullable [Value].
  T? get() => _left;

  /// Get [U] value, may throw an exception.
  U unwrap<U>() => _unwrap<U>();

  /// Get **async** [U] value, may throw an exception.
  Future<U> unwrapAsync<U>() => _unwrapAsync<U>();

  /// Match [Value] and [None].
  void match(
    void Function(T value) value,
    void Function() none,
  ) =>
      _fold<void>(
        (left) => value(left),
        (right) => none(),
      );
  
  /// Match **async** [Value] and [None].
  Future<void> matchAsync(
    Future<void> Function(T value) value,
    Future<void> Function() none,
  ) =>
      _foldAsync<void>(
        (left) => value(left),
        (right) => none(),
      );

  /// If [Value] is present, invoke the specified consumer
  /// with the value, otherwise do nothing.
  void ifPresent(
    void Function(T value) consumer,
  ) =>
      _fold<void>(
        (left) => consumer(left),
        (right) => {},
      );

  /// Return the [Value] if present, otherwise return [other].
  T orElse(
    T other,
  ) =>
      _fold<T>(
        (left) => left,
        (right) => other,
      );

  /// Return the [Value] if present, otherwise call and return result
  /// of [supplier].
  T orElseLazy(
    T Function() supplier,
  ) =>
      _fold<T>(
        (left) => left,
        (right) => supplier(),
      );

  /// Returns [res] if the [Option] is [Value], otherwise returns
  /// the [None] value of this.
  Option<U> and<U>(Option<U> res) => _and<U>(res) as Option<U>;

  /// Returns [res] if the [Option] is [None], otherwise returns
  /// the [Value] of this.
  Option<T> or(Option<T> res) => _or<void>(res) as Option<T>;

  /// Returns true if the result is a [Value].
  bool contains<U>(U x) => _contains(x);

  /// Returns the contained [Value]. Throw [OptionException] on [None].
  T expect(String msg) => _expect(msg);

  /// Maps a [Option<T>] to [Option<U>] by applying a function to a
  /// contained [Value].
  Option<U> map<U>(U Function(T value) mapper) => _map(mapper) as Option<U>;

  /// Maps a [Option<T>] to [Option<U>] by applying an **async** function
  /// to a contained [Value].
  Future<Option<U>> mapAsync<U>(Future<U> Function(T value) mapper) async =>
      await _mapAsync(mapper) as Option<U>;

  /// Constructs an [Option] with the specified present non-null [Value].
  static Value<T> of<T>(T value) {
    if (value == null) {
      throw const OptionException("Value can' be null. Use `ofNullable()`");
    }
    return ofNullable(value) as Value<T>;
  }

  /// Constructs an [Option] describing the specified [Value],
  /// if non-null, otherwise returns a [None].
  static Option<T> ofNullable<T>(T? value) {
    if (value != null) {
      return Value(value);
    } else {
      return None<T>();
    }
  }

  /// Constructs an [Option] with the specified present non-null [Value].
  static Value<T> ofLazy<T>(T Function() value) {
    final v = value();
    if (v == null) {
      throw const OptionException("Value can' be null. Use `ofNullableLazy()`");
    }
    return ofNullable(v) as Value<T>;
  }

  /// Constructs an [Option] describing the specified [Value],
  /// if non-null, otherwise returns a [None].
  static Option<T> ofNullableLazy<T>(T? Function() value) {
    final v = value();
    if (v != null) {
      return Value(v);
    } else {
      return None<T>();
    }
  }
}

class Value<T> extends Option<T> with _Left<T, void> {

  /// {@macro ok}
  const Value(this.value) : super._();
  final T value;

  @override
  _EitherBase<U, void> _and<U>(_EitherBase<U, void> res) => res as Option<U>;

  @override
  bool _contains<U>(U x) => value == x;

  @override
  _EitherBase<L, R> _either<L, R>(
    L Function(T left) fnL,
    R Function(void right) fnR,
  ) =>
      throw UnimplementedError();

  @override
  Future<_EitherBase<L, R>> _eitherAsync<L, R>(
    Future<L> Function(T left) fnL,
    Future<R> Function(void right) fnR,
  ) =>
      throw UnimplementedError();

  @override
  T _expect(String msg) => value;

  @override
  void _expectErr(String msg) => throw UnimplementedError();

  @override
  U _fold<U>(U Function(T left) fnL, U Function(void right) fnR) => fnL(value);

  @override
  Future<U> _foldAsync<U>(
    Future<U> Function(T left) fnL,
    Future<U> Function(void right) fnR,
  ) =>
      fnL(value);

  @override
  _EitherBase<U, void> _map<U>(U Function(T left) mapper) =>
      Value(mapper(value));

  @override
  Future<_EitherBase<U, void>> _mapAsync<U>(
    Future<U> Function(T left) mapper,
  ) =>
      mapper(value).then(Value.new);

  @override
  _EitherBase<T, F> _mapErr<F>(F Function(void right) mapper) =>
      throw UnimplementedError();

  @override
  Future<_EitherBase<T, F>> _mapErrAsync<F>(
    Future<F> Function(void right) mapper,
  ) =>
      throw UnimplementedError();

  @override
  _EitherBase<T, F> _or<F>(_EitherBase<T, F> res) => this as _EitherBase<T, F>;
}

class None<T> extends Option<T> with _Right<T, void> {
  /// {@macro ok}
  const None() : super._();

  @override
  _EitherBase<U, void> _and<U>(_EitherBase<U, void> res) => this as Option<U>;

  @override
  bool _contains<U>(U x) => false;

  @override
  _EitherBase<L, R> _either<L, R>(
    L Function(T left) fnL,
    R Function(void right) fnR,
  ) =>
      throw UnimplementedError();

  @override
  Future<_EitherBase<L, R>> _eitherAsync<L, R>(
    Future<L> Function(T left) fnL,
    Future<R> Function(void right) fnR,
  ) =>
      throw UnimplementedError();

  @override
  T _expect(String msg) => throw OptionException(msg);

  @override
  void _expectErr(String msg) => throw UnimplementedError();

  @override
  U _fold<U>(U Function(T left) fnL, U Function(void right) fnR) => fnR(null);

  @override
  Future<U> _foldAsync<U>(
    Future<U> Function(T left) fnL,
    Future<U> Function(void right) fnR,
  ) =>
      fnR(null);

  @override
  _EitherBase<U, void> _map<U>(U Function(T left) mapper) => None<U>();

  @override
  Future<_EitherBase<U, void>> _mapAsync<U>(
    Future<U> Function(T left) mapper,
  ) =>
      Future.value(None<U>());

  @override
  _EitherBase<T, F> _mapErr<F>(F Function(void right) mapper) =>
      throw UnimplementedError();

  @override
  Future<_EitherBase<T, F>> _mapErrAsync<F>(
    Future<F> Function(void right) mapper,
  ) =>
      throw UnimplementedError();

  @override
  _EitherBase<T, F> _or<F>(_EitherBase<T, F> res) => res;
}
