/*
This file is part of Intake24.

© Crown copyright, 2012, 2013, 2014.

This software is licensed under the Open Government Licence 3.0:

http://www.nationalarchives.gov.uk/doc/open-government-licence/
*/

package org.workcraft.gwt.shared.client;

import java.util.NoSuchElementException;
import java.util.function.Function;

import com.google.gwt.user.client.rpc.IsSerializable;

public abstract class Option<T> implements IsSerializable {
  public interface Visitor<T, R> {
    public R visitSome(T item);

    public R visitNone();
  }

  public interface SideEffectVisitor<T> {
    public void visitSome(T item);

    public void visitNone();
  }

  public static class Some<T> extends Option<T> implements IsSerializable {
    public T item;

    @Deprecated
    public Some() {
    }

    public Some(T item) {
      this.item = item;
    }

    @Override
    public <R> R accept(Visitor<T, R> visitor) {
      return visitor.visitSome(item);
    }

    @Override
    public void accept(SideEffectVisitor<T> visitor) {
      visitor.visitSome(item);
    }

    @Override
    public String toString() {
      return "Some(" + item.toString() + ")";
    }
  }

  public static class None<T> extends Option<T> implements IsSerializable {
    @Override
    public <R> R accept(Visitor<T, R> visitor) {
      return visitor.visitNone();
    }

    @Override
    public void accept(SideEffectVisitor<T> visitor) {
      visitor.visitNone();
    }

    @Override
    public String toString() {
      return "None";
    }
  }

  public abstract <R> R accept(Visitor<T, R> visitor);

  public abstract void accept(SideEffectVisitor<T> visitor);

  public static <T> Option<T> none() {
    return new Option.None<T>();
  }

  public static <T> Option<T> some(T item) {
    return new Option.Some<T>(item);
  }

  public static <T> Option<T> fromNullable(T item) {
    if (item == null)
      return none();
    else
      return some(item);
  }

  public boolean isDefined() {
    return !isEmpty();
  }

  public boolean isEmpty() {
    return accept(new Visitor<T, Boolean>() {
      @Override
      public Boolean visitSome(T item) {
        return false;
      }

      @Override
      public Boolean visitNone() {
        return true;
      }
    });
  }

  public T getOrElse(final T def) {
    return accept(new Visitor<T, T>() {
      @Override
      public T visitSome(T item) {
        return item;
      }

      @Override
      public T visitNone() {
        return def;
      }
    });
  }

  public T getOrDie(final String message) {
    return accept(new Visitor<T, T>() {
      @Override
      public T visitSome(T item) {
        return item;
      }

      @Override
      public T visitNone() {
        throw new NoSuchElementException(message);
      }
    });
  }

  public T getOrDie() {
    return getOrDie("getOrDie from a None");
  }

  public <T2> Option<T2> map(final Function1<T, T2> func) {
    return this.accept(new Visitor<T, Option<T2>>() {

      @Override
      public Option<T2> visitSome(T item) {
        return new Option.Some<T2>(func.apply(item));
      }

      @Override
      public Option<T2> visitNone() {
        return new Option.None<T2>();
      }
    });
  }

  public boolean equalTo(final Option<T> other) {
    return accept(new Option.Visitor<T, Boolean>() {
      @Override
      public Boolean visitSome(final T item1) {
        return other.accept(new Option.Visitor<T, Boolean>() {
          @Override
          public Boolean visitSome(T item2) {
            return item1.equals(item2);
          }

          @Override
          public Boolean visitNone() {
            return false;
          }
        });
      }

      @Override
      public Boolean visitNone() {
        return other.accept(new Option.Visitor<T, Boolean>() {
          @Override
          public Boolean visitSome(T item) {
            return false;
          }

          @Override
          public Boolean visitNone() {
            return true;
          }
        });
      }
    });
  }

  public static <T, R> Visitor<T,R> makeVisitor(final Function1<T, R> ifDefined, final Function0<R> ifEmpty) {
    return new Visitor<T, R>() {
      @Override
      public R visitSome(T item) {
        return ifDefined.apply(item);
      }

      @Override
      public R visitNone() {
        return ifEmpty.apply();
      }
    };
  }

  @SuppressWarnings("unchecked")
  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (obj.getClass() != this.getClass())
      return false;
    return equalTo((Option<T>) obj);
  }
}

@@ -309,24 +309,24 @@
 unction.
-Function
+Supplier
 ;%0A%0Aimpor
@@ -942,32 +942,149 @@
 e(item);%0A    %7D%0A%0A
+    @Override%0A    public %3CR%3E R match(Function1%3CT, R%3E some, Supplier%3CR%3E none) %7B%0A      return some.apply(item);%0A    %7D%0A%0A
     @Override%0A  
@@ -1448,32 +1448,143 @@
 tNone();%0A    %7D%0A%0A
+    @Override%0A    public %3CR%3E R match(Function1%3CT, R%3E some, Supplier%3CR%3E none) %7B%0A      return none.get();%0A    %7D%0A%0A
     @Override%0A  
@@ -1739,32 +1739,32 @@
 ne%22;%0A    %7D%0A  %7D%0A%0A
-
   public abstrac
@@ -1795,32 +1795,104 @@
 , R%3E visitor);%0A%0A
+  public abstract %3CR%3E R match(Function1%3CT, R%3E some, Supplier%3CR%3E none);%0A%0A
   public abstrac

Add Java 8 friendly match function