package cs2030s.fp;

/**
 * A container class the encapsulate a
 * lazily-evaluated-and-memoized value.
 *
 * @author Adi Yoga S. Prabawa
 * @version CS2030S AY 22/23 Sem 1
 */
public class Memo<T> implements Immutatorable<T> {
  
  private Constant<? extends T> com;
  private Actually<T> val;
  
  private Memo(Actually<T> val, Constant<T> com) {
    this.com = com;
    this.val = val;
  }
  
  public static <T> Memo<T> from(T val) {
    return new Memo<T>(Actually.ok(val), null);
  }
  public static <T> Memo<T> from(Constant<T> com) {
    return new Memo<T>(Actually.err(), com);
  }
  
  public T get() {
    this.eval();
    return this.val.unless(null);
  }
  private void eval() {
    if (this.com != null) {
      this.val = Actually.<T>ok(this.com.init());
      this.com = null;
    }
  }
  
  @Override
  public <R> Memo<R> transform(Immutator<? extends R, ? super T> f) {
    return Memo.<R>from(() -> f.invoke(this.get()));
  }
  public <R> Memo<R> next(Immutator<? extends Memo<? extends R>, ? super T> f) {
    return Memo.<R>from(() -> f.invoke(this.get()).get());
  }
  public <S, R> Memo<R> combine(Memo<S> snd, Combiner<? extends R, ? super T, ? super S> f) {
    return Memo.<R>from(() -> f.combine(this.get(), snd.get()));
  }
  public Memo<Boolean> check(Immutator<Boolean, ? super T> pred) {
    return Memo.<Boolean>from(() -> pred.invoke(this.get()));
  }
  
  @Override
  public String toString() {
    if (this.com != null) {
      return "?";
    }
    return this.get().toString();
  }
  
  @Override
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if (!(obj instanceof Memo<?>)) {
      return false;
    }
    Memo<?> that = (Memo<?>) obj;
    that.eval();
    this.eval();
    return this.val.equals(that.val);
  }
}