package org.metaborg.meta.nabl2.relations.terms;

import java.io.Serializable;
import java.util.Map;
import java.util.Optional;

import org.metaborg.meta.nabl2.terms.ITerm;
import org.metaborg.meta.nabl2.terms.Terms.IMatcher;
import org.metaborg.meta.nabl2.terms.Terms.M;
import org.metaborg.meta.nabl2.unification.UnificationException;
import org.metaborg.meta.nabl2.unification.Unifier;
import org.metaborg.meta.nabl2.util.functions.PartialFunction1;
import org.metaborg.meta.nabl2.util.tuples.ImmutableTuple2;
import org.metaborg.meta.nabl2.util.tuples.Tuple2;

import com.google.common.collect.Maps;

public class FunctionTerms {

    public static IMatcher<Map<String, PartialFunction1<ITerm, ITerm>>> functions() {
        return M.listElems(function(), (l, funDefs) -> {
            Map<String, PartialFunction1<ITerm, ITerm>> functions = Maps.newHashMap();
            for(Tuple2<String, PartialFunction1<ITerm, ITerm>> funDef : funDefs) {
                functions.put(funDef._1(), funDef._2());
            }
            return functions;
        });
    }

    private static IMatcher<Tuple2<String, PartialFunction1<ITerm, ITerm>>> function() {
        return M.tuple2(functionName(), M.listElems(functionCase()), (t, name, cases) -> {
            PartialFunction1<ITerm, ITerm> f =
                    (PartialFunction1<ITerm, ITerm> & Serializable) term -> eval(cases, term);
            return ImmutableTuple2.of(name, f);
        });
    }

    private static IMatcher<Tuple2<ITerm, ITerm>> functionCase() {
        return M.tuple2(M.term(), M.term(), (t, t1, t2) -> {
            if(!t1.getVars().containsAll(t2.getVars())) {
                throw new IllegalStateException("Function case is not closed.");
            }
            return ImmutableTuple2.of(t1, t2);
        });
    }

    private static IMatcher<String> functionName() {
        return M.appl1("Function", M.stringValue(), (t, n) -> n);
    }

    private static Optional<ITerm> eval(Iterable<Tuple2<ITerm, ITerm>> cases, ITerm term) {
        if(!term.isGround()) {
            throw new IllegalStateException("Term argument must be ground.");
        }
        for(Tuple2<ITerm, ITerm> c : cases) {
            Unifier<?> unifier = new Unifier<>();
            try {
                unifier.unify(c._1(), term);
                ITerm result = unifier.find(c._2());
                return Optional.of(result);
            } catch(UnificationException e) {
            }
        }
        return Optional.empty();
    }

}
@@ -72,16 +72,39 @@
 izable;%0A
+import java.util.List;%0A
 import j
@@ -1268,150 +1268,8 @@
 %3E %7B%0A
-            PartialFunction1%3CITerm, ITerm%3E f =%0A                    (PartialFunction1%3CITerm, ITerm%3E & Serializable) term -%3E eval(cases, term);%0A
     
@@ -1308,17 +1308,31 @@
 f(name, 
-f
+new Eval(cases)
 );%0A     
@@ -1841,67 +1841,340 @@
 tic 
-Optional%3CITerm%3E eval(Iterable%3CTuple2%3CITerm, ITerm%3E%3E cases, 
+class Eval implements PartialFunction1%3CITerm, ITerm%3E, Serializable %7B%0A        private static final long serialVersionUID = 42L;%0A%0A        private final List%3CTuple2%3CITerm, ITerm%3E%3E cases;%0A%0A        private Eval(List%3CTuple2%3CITerm, ITerm%3E%3E cases) %7B%0A            this.cases = cases;%0A        %7D%0A%0A        @Override public Optional%3CITerm%3E apply(
 ITer
@@ -2179,24 +2179,28 @@
 erm term) %7B%0A
+    
         if(!
@@ -2214,24 +2214,28 @@
 Ground()) %7B%0A
+    
             
@@ -2300,34 +2300,42 @@
 und.%22);%0A        
+  
-%7D%0A
+  %7D%0A    
         for(Tupl
@@ -2376,16 +2376,20 @@
         
+    
 Unifier%3C
@@ -2434,14 +2434,22 @@
     
+ 
+   
 try %7B%0A
+    
     
@@ -2489,16 +2489,20 @@
  term);%0A
+    
         
@@ -2554,32 +2554,36 @@
                 
+    
 return Optional.
@@ -2594,16 +2594,20 @@
 esult);%0A
+    
         
@@ -2648,34 +2648,42 @@
 ) %7B%0A            
+   
+ 
 %7D%0A
+    
         %7D%0A      
@@ -2668,32 +2668,36 @@
 %7D%0A            %7D%0A
+    
         return O
@@ -2709,20 +2709,32 @@
 al.empty();%0A
+%0A        %7D%0A%0A
     %7D%0A%0A%7D

Create static class instead of lambda to avoid accidental capture of the outer class.