package org.spoofax.jsglr.client.imploder;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import org.metaborg.util.functions.PartialFunction2;
import org.spoofax.interpreter.terms.IStrategoAppl;
import org.spoofax.interpreter.terms.IStrategoConstructor;
import org.spoofax.interpreter.terms.IStrategoList;
import org.spoofax.interpreter.terms.IStrategoTerm;
import org.spoofax.interpreter.terms.IStrategoTuple;
import org.spoofax.interpreter.terms.ITermFactory;

import com.google.common.collect.ImmutableList;

public class Injections {

    private final ITermFactory factory;
    private final PartialFunction2<String, String, String> injName;

    public Injections(ITermFactory factory, PartialFunction2<String, String, String> injName) {
        this.factory = factory;
        this.injName = injName;
    }

    public IStrategoTerm explicate(final IStrategoTerm term) {
        final List<String> injections = ImmutableList.copyOf(ImploderAttachment.get(term).getInjections()).reverse();

        IStrategoTerm result;
        String sort;

        switch(term.getTermType()) {
            case IStrategoTerm.APPL: {
                final IStrategoAppl appl = (IStrategoAppl) term;
                final IStrategoTerm[] subterms = Arrays.stream(appl.getAllSubterms()).map(this::explicate)
                        .collect(Collectors.toList()).toArray(new IStrategoTerm[appl.getSubtermCount()]);
                result = factory.makeAppl(appl.getConstructor(), subterms, appl.getAnnotations());
                sort = ImploderAttachment.get(term).getSort();
                break;
            }
            case IStrategoTerm.TUPLE: {
                final IStrategoTuple tuple = (IStrategoTuple) term;
                final IStrategoTerm[] subterms = Arrays.stream(tuple.getAllSubterms()).map(this::explicate)
                        .collect(Collectors.toList()).toArray(new IStrategoTerm[tuple.getSubtermCount()]);
                result = factory.makeTuple(subterms, tuple.getAnnotations());
                final List<String> componentSorts = Arrays.stream(term.getAllSubterms())
                        .map(t -> ImploderAttachment.get(t).getSort()).collect(Collectors.toList());
                sort = "Tuple_" + String.join("_", componentSorts) + "_";
                break;
            }
            case IStrategoTerm.LIST: {
                final IStrategoList list = (IStrategoList) term;
                final IStrategoTerm[] subterms = Arrays.stream(list.getAllSubterms()).map(this::explicate)
                        .collect(Collectors.toList()).toArray(new IStrategoTerm[list.getSubtermCount()]);
                result = factory.makeList(subterms, list.getAnnotations());
                final String elementSort = ImploderAttachment.get(term).getElementSort();
                sort = "List_" + elementSort + "_";
                break;
            }
            default:
                result = term;
                sort = ImploderAttachment.get(term).getSort();
                break;
        }
        result = factory.copyAttachments(term, result);
        ImploderAttachment.get(result).clearInjections();

        for(String injection : injections) {
            final Optional<String> name = injName.apply(sort, injection);
            if(!name.isPresent()) {
                continue;
            }
            final IStrategoConstructor cons = factory.makeConstructor(name.get(), 1);
            ImploderAttachment ia = ImploderAttachment.get(result);
            result = factory.makeAppl(cons, result);
            sort = injection;
            if(ia != null) {
                ImploderAttachment.putImploderAttachment(result, false, sort, ia.getLeftToken(), ia.getRightToken(),
                        false, false, false, false);
            }
        }

        return result;
    }

}
@@ -614,147 +614,107 @@
 ate 
-final ITermFactory factory;%0A    private final PartialFunction2%3CString, String, String%3E injName;%0A%0A    public Injections(ITermFactory factory
+static final String INJ_ANNO = %22inj%22;%0A%0A    public static IStrategoTerm explicate(IStrategoTerm term
 , Pa
@@ -759,19 +759,17 @@
  injName
-) %7B
+,
 %0A       
@@ -773,130 +773,32 @@
     
-this.factory = factory;%0A        this.injName = injName;%0A    %7D%0A%0A    public IStrategoTerm explicate(final IStrategoTerm term
+    ITermFactory factory
 ) %7B%0A
@@ -1149,32 +1149,56 @@
 erm%5B%5D subterms =
+%0A                       
  Arrays.stream(a
@@ -1223,33 +1223,73 @@
 ()).map(
-this::explicate)%0A
+subterm -%3E explicate(subterm, injName, factory))%0A        
         
@@ -1733,32 +1733,56 @@
 erm%5B%5D subterms =
+%0A                       
  Arrays.stream(t
@@ -1808,33 +1808,73 @@
 ()).map(
-this::explicate)%0A
+subterm -%3E explicate(subterm, injName, factory))%0A        
         
@@ -2503,16 +2503,40 @@
 bterms =
+%0A                       
  Arrays.
@@ -2573,25 +2573,65 @@
 map(
-this::explicate)%0A
+subterm -%3E explicate(subterm, injName, factory))%0A        
     
@@ -3531,16 +3531,160 @@
 (), 1);%0A
+            final IStrategoTerm anno =%0A                    factory.makeAppl(INJ_ANNO, factory.makeString(sort), factory.makeString(injection));%0A
         
@@ -3787,22 +3787,70 @@
 l(cons, 
-result
+new IStrategoTerm%5B%5D %7B result %7D, factory.makeList(anno)
 );%0A     

Tag injections with an annotation.