package com.github.kmizu.jcombinator.example.regex;

import com.github.kmizu.jcombinator.ParseResult;
import com.github.kmizu.jcombinator.datatype.Function0;
import com.github.kmizu.jcombinator.datatype.Function1;

public class RegexInterpreter {
    interface SuccessfullContinuation extends Function0<Boolean> {}
    private static class ExpressionRewriter implements AstNode.RVisitor<AstNode.RExpression, Void> {
        @Override
        public AstNode.RExpression visitRGrouped(AstNode.RGrouped node, Void context) {
            return new AstNode.RGrouped(node.getTarget().accept(this, context));
        }

        @Override
        public AstNode.RExpression visitRString(AstNode.RString node, Void context) {
            return node;
        }

        @Override
        public AstNode.RExpression visitRChoice(AstNode.RChoice node, Void context) {
            return new AstNode.RChoice(
                node.getLhs().accept(this, context),
                node.getRhs().accept(this, context)
            );
        }

        @Override
        public AstNode.RExpression visitRSequence(AstNode.RSequence node, Void context) {
            return new AstNode.RSequence(
                node.getLhs().accept(this, context),
                node.getRhs().accept(this, context)
            );
        }

        @Override
        public AstNode.RExpression visitRRepeat0(AstNode.RRepeat0 node, Void context) {
            return new AstNode.RRepeat0(
                node.getTarget().accept(this, context)
            );
        }

        @Override
        public AstNode.RExpression visitRRepeat1(AstNode.RRepeat1 node, Void context) {
            AstNode.RExpression target = node.getTarget().accept(this, context);
            return new AstNode.RSequence(
                target,
                new AstNode.RRepeat0(target)
            );
        }
    }
    private static class ExpressionInterpreter implements  AstNode.RVisitor<Boolean, SuccessfullContinuation> {
        private String input;
        private int cursor;

        public ExpressionInterpreter(String input) {
            this.input = input;
            this.cursor = cursor;
        }

        @Override
        public Boolean visitRGrouped(AstNode.RGrouped node, SuccessfullContinuation context) {
            return node.getTarget().accept(this, context);
        }

        @Override
        public Boolean visitRString(AstNode.RString node, SuccessfullContinuation context) {
            String slice = input.substring(cursor);
            if(slice.startsWith(node.getLiteral())) {
                cursor += node.getLiteral().length();
                return context.invoke();
            } else {
                return false;
            }
        }

        @Override
        public Boolean visitRChoice(AstNode.RChoice node, SuccessfullContinuation context) {
            final int start = cursor;
            final Function0<Boolean> onFailAlt = () -> {
                cursor = start;
                return node.getRhs().accept(this, context);
            };
            final SuccessfullContinuation onSuccess = () -> {
                if(context.invoke()) {
                    return true;
                } else {
                    return onFailAlt.invoke();
                }
            };
            return node.getLhs().accept(this, onSuccess);
        }

        @Override
        public Boolean visitRSequence(AstNode.RSequence node, SuccessfullContinuation context) {
            return node.getLhs().accept(this, () ->
                node.getRhs().accept(this, context)
            );
        }

        @Override
        public Boolean visitRRepeat0(AstNode.RRepeat0 node, SuccessfullContinuation context) {
            Function1<Function0<Boolean>, Boolean>[] onSuccessRep = new Function1[1];
            onSuccessRep[0] = (f) -> {
                final int start = cursor;
                final SuccessfullContinuation nf = () -> {
                    cursor = start;
                    return context.invoke() ? true : f.invoke();
                };
                return (node.getTarget().accept(
                    this,
                    () -> onSuccessRep[0].invoke(nf)
                ) ? true : nf.invoke());
            };
            return onSuccessRep[0].invoke(context);
        }

        @Override
        public Boolean visitRRepeat1(AstNode.RRepeat1 node, SuccessfullContinuation context) {
            throw new UnsupportedOperationException("ExpressionInterpreter#visit(AstNode.RRepeat1, SuccessfullContinuation)");
        }
    }

    public boolean matches(String regex, String input) {
        ParseResult<AstNode.RExpression> result = new RegexParser().expression().invoke(regex);
        boolean[] matchResult = new boolean[1];
        matchResult[0] = false;
        result.onSuccess(e -> {
            ExpressionRewriter rewriter = new ExpressionRewriter();
            AstNode.RExpression newE = e.value().accept(rewriter, null);
            ExpressionInterpreter interpreter = new ExpressionInterpreter(input);
            matchResult[0] = newE.accept(interpreter, () -> true);
        });
        return matchResult[0];
    }

    public static void main(String[] args) {
        RegexInterpreter i = new RegexInterpreter();
        System.out.println(i.matches("a*aaaa", "aaa"));
    }
}

@@ -2897,107 +2897,28 @@
     
-final Function0%3CBoolean%3E onFailAlt = () -%3E %7B%0A                cursor = start;%0A                return
+boolean lhsMatched =
  nod
@@ -2914,33 +2914,33 @@
 tched = node.get
-R
+L
 hs().accept(this
@@ -2967,116 +2967,25 @@
     
-%7D;%0A            final SuccessfullContinuation onSuccess = () -%3E %7B%0A                if(context.invoke()) %7B%0A    
+if(lhsMatched) %7B%0A
     
@@ -3013,36 +3013,32 @@
 ue;%0A            
- 
-   
 %7D else %7B%0A       
@@ -3050,37 +3050,22 @@
     
-    return onFailAlt.invoke()
+cursor = start
 ;%0A  
@@ -3080,37 +3080,8 @@
     
- 
- %7D%0A            %7D;%0A          
   re
@@ -3085,33 +3085,33 @@
  return node.get
-L
+R
 hs().accept(this
@@ -3116,19 +3116,31 @@
 is, 
+c
 on
-Success);
+text);%0A            %7D
 %0A   
@@ -5105,16 +5105,95 @@
 *aaa
-a
 %22, %22aaa
+%22));%0A        System.out.println(i.matches(%22(a*%7Cxxx)y(bca)*(x%7Cy)d%22, %22xxxybcabcaxd
 %22));

Improve implementation