package org.jooby.internal;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.Map;
import java.util.function.Consumer;

import org.junit.Test;

public class RoutePatternTest {

  class RoutePathAssert {

    RoutePattern path;

    public RoutePathAssert(final String method, final String pattern, boolean ignoreCase) {
      path = new RoutePattern(method, pattern, ignoreCase);
    }

    public RoutePathAssert(final String method, final String pattern) {
      this(method, pattern, false);
    }

    public RoutePathAssert matches(final String path) {
      return matches(path, (vars) -> {
      });
    }

    public RoutePathAssert matches(final String path, final Consumer<Map<Object, String>> vars) {
      String message = this.path + " != " + path;
      RouteMatcher matcher = this.path.matcher(path);
      boolean matches = matcher.matches();
      if (!matches) {
        System.err.println(message);
      }
      assertTrue(message, matches);
      vars.accept(matcher.vars());
      return this;
    }

    public RoutePathAssert butNot(final String path) {
      String message = this.path + " == " + path;
      RouteMatcher matcher = this.path.matcher(path);
      boolean matches = matcher.matches();
      if (matches) {
        System.err.println(message);
      }
      assertFalse(message, matches);
      return this;
    }
  }

  @Test
  public void fixed() {
    new RoutePathAssert("GET", "com/test.jsp")
        .matches("GET/com/test.jsp")
        .butNot("GET/com/tsst.jsp");
  }

  @Test
  public void multipleVerb() {
    new RoutePathAssert("get|POST", "com/test.jsp")
        .matches("GET/com/test.jsp")
        .matches("POST/com/test.jsp")
        .butNot("PUT/com/test.jsp")
        .butNot("DELETE/com/test.jsp")
        .butNot("GET/com/tsst.jsp");
  }

  @Test
  public void anyVerb() {
    new RoutePathAssert("*", "com/test.jsp")
        .matches("GET/com/test.jsp")
        .matches("POST/com/test.jsp")
        .butNot("GET/com/tsst.jsp");

    new RoutePathAssert("*", "user/:id")
        .matches("GET/user/xid", (vars) -> {
          assertEquals("xid", vars.get("id"));
        })
        .matches("POST/user/xid2", (vars) -> {
          assertEquals("xid2", vars.get("id"));
        })
        .butNot("GET/com/tsst.jsp");
  }

  @Test
  public void wildOne() {
    new RoutePathAssert("GET", "com/t?st.jsp")
        .matches("GET/com/test.jsp")
        .matches("GET/com/tsst.jsp")
        .matches("GET/com/tast.jsp")
        .matches("GET/com/txst.jsp")
        .butNot("GET/com/test1.jsp");
  }

  @Test
  public void wildMany() {
    new RoutePathAssert("GET", "/profile/*/edit")
        .matches("GET/profile/ee-00-9-k/edit")
        .butNot("GET/profile/ee-00-9-k/p/edit");

    new RoutePathAssert("GET", "/profile/*/*/edit")
        .matches("GET/profile/ee-00-9-k/p/edit")
        .butNot("GET/profile/ee-00-9-k/edit")
        .butNot("GET/profile/ee-00-9-k/p/k/edit");
  }

  @Test
  public void subdir() {
    new RoutePathAssert("GET", "com/**/test.jsp")
        .matches("GET/com/test.jsp")
        .matches("GET/com/a/test.jsp")
        .butNot("GET/com/a/testx.jsp")
        .butNot("GET/org/test.jsp");

    new RoutePathAssert("GET", "com/**")
        .matches("GET/com/test.jsp")
        .matches("GET/com/a/test.jsp")
        .matches("GET/com/a/testx.jsp")
        .butNot("GET/org/test.jsp");

  }

  @Test
  public void any() {
    new RoutePathAssert("GET", "com/**")
        .matches("GET/com/test.jsp")
        .matches("GET/com/a/test.jsp")
        .matches("GET/com/a/testx.jsp")
        .butNot("GET/org/test.jsp");
  }

  @Test
  public void any2() {
    new RoutePathAssert("GET", "org/**/servlet/*.html")
        .matches("GET/org/jooby/servlet/test.html")
        .matches("GET/org/jooby/a/servlet/test.html")
        .matches("GET/org/jooby/a/b/c/servlet/test.html")
        .butNot("GET/org/jooby/a/b/c/servlet/test.js");
  }

  @Test
  public void anyNamed() {
    new RoutePathAssert("GET", "com/**:rest")
        .matches("GET/com/test.jsp", vars -> assertEquals("test.jsp", vars.get("rest")))
        .matches("GET/com/a/test.jsp", vars -> assertEquals("a/test.jsp", vars.get("rest")))
        .matches("GET/com/", vars -> assertEquals("", vars.get("rest")))
        .butNot("GET/com")
        .butNot("GET/test.jsp");
  }

  @Test
  public void anyNamedSpring() {
    new RoutePathAssert("GET", "com/{rest:**}")
        .matches("GET/com/test.jsp", vars -> assertEquals("test.jsp", vars.get("rest")))
        .matches("GET/com/a/test.jsp", vars -> assertEquals("a/test.jsp", vars.get("rest")))
        .matches("GET/com/", vars -> assertEquals("", vars.get("rest")))
        .butNot("GET/com")
        .butNot("GET/test.jsp");
  }

  @Test
  public void anyNamedInner() {
    new RoutePathAssert("GET", "com/**:rest/bar")
        .matches("GET/com/foo/bar", (vars) -> assertEquals("foo", vars.get("rest")))
        .matches("GET/com/a/foo/bar", (vars) -> assertEquals("a/foo", vars.get("rest")))
        .butNot("GET/com/foo/baz")
        .butNot("GET/test.jsp");
  }

  @Test
  public void anyNamedInnerSpring() {
    new RoutePathAssert("GET", "com/{rest:**}/bar")
        .matches("GET/com/foo/bar", (vars) -> assertEquals("foo", vars.get("rest")))
        .matches("GET/com/a/foo/bar", (vars) -> assertEquals("a/foo", vars.get("rest")))
        .butNot("GET/com/foo/baz")
        .butNot("GET/test.jsp");
  }

  @Test
  public void anyNamedMulti() {
    new RoutePathAssert("GET", "com/**:first/bar/**:second")
        .matches("GET/com/foo/bar/moo", (vars) -> {
          assertEquals("foo", vars.get("first"));
          assertEquals("moo", vars.get("second"));
        })
        .matches("GET/com/a/foo/bar/moo/baz", (vars) -> {
          assertEquals("a/foo", vars.get("first"));
          assertEquals("moo/baz", vars.get("second"));
        })
        .butNot("GET/com/foo/baz")
        .butNot("GET/test.jsp");
  }

  @Test
  public void anyNamedMultiSpring() {
    new RoutePathAssert("GET", "com/{first:**}/bar/{second:**}")
        .matches("GET/com/foo/bar/moo", (vars) -> {
          assertEquals("foo", vars.get("first"));
          assertEquals("moo", vars.get("second"));
        })
        .matches("GET/com/a/foo/bar/moo/baz", (vars) -> {
          assertEquals("a/foo", vars.get("first"));
          assertEquals("moo/baz", vars.get("second"));
        })
        .butNot("GET/com/foo/baz")
        .butNot("GET/test.jsp");
  }

  @Test
  public void anyNamedMultiMixed() {
    new RoutePathAssert("GET", "com/**:first/bar/{second:**}")
        .matches("GET/com/foo/bar/moo", (vars) -> {
          assertEquals("foo", vars.get("first"));
          assertEquals("moo", vars.get("second"));
        })
        .matches("GET/com/a/foo/bar/moo/baz", (vars) -> {
          assertEquals("a/foo", vars.get("first"));
          assertEquals("moo/baz", vars.get("second"));
        })
        .butNot("GET/com/foo/baz")
        .butNot("GET/test.jsp");
  }

  @Test
  public void anyNamedMultiMixed2() {
    new RoutePathAssert("GET", "com/{first:**}/bar/**:second")
        .matches("GET/com/foo/bar/moo", (vars) -> {
          assertEquals("foo", vars.get("first"));
          assertEquals("moo", vars.get("second"));
        })
        .matches("GET/com/a/foo/bar/moo/baz", (vars) -> {
          assertEquals("a/foo", vars.get("first"));
          assertEquals("moo/baz", vars.get("second"));
        })
        .butNot("GET/com/foo/baz")
        .butNot("GET/test.jsp");
  }

  @Test
  public void rootVar() {
    new RoutePathAssert("GET", "{id}/list")
        .matches("GET/xqi/list")
        .matches("GET/123/list")
        .butNot("GET/123/lisx");
  }

  @Test
  public void mixedVar() {
    new RoutePathAssert("GET", "user/:id/:name")
        .matches("GET/user/xqi/n", (vars) -> {
          assertEquals("xqi", vars.get("id"));
          assertEquals("n", vars.get("name"));
        })
        .butNot("GET/user/123/x/y");

    new RoutePathAssert("GET", "user/{id}/{name}")
        .matches("GET/user/xqi/n", (vars) -> {
          assertEquals("xqi", vars.get("id"));
          assertEquals("n", vars.get("name"));
        })
        .butNot("GET/user/123/x/y");

    new RoutePathAssert("GET", "user/{id}/:name")
        .matches("GET/user/xqi/n", (vars) -> {
          assertEquals("xqi", vars.get("id"));
          assertEquals("n", vars.get("name"));
        })
        .butNot("GET/user/123/x/y");

    new RoutePathAssert("GET", "user/:id/{name}")
        .matches("GET/user/xqi/n", (vars) -> {
          assertEquals("xqi", vars.get("id"));
          assertEquals("n", vars.get("name"));
        })
        .butNot("GET/user/123/x/y");
  }

  @Test
  public void var() {
    new RoutePathAssert("GET", "user/{id}")
        .matches("GET/user/xqi", (vars) -> {
          assertEquals("xqi", vars.get("id"));
        })
        .matches("GET/user/123", (vars) -> {
          assertEquals("123", vars.get("id"));
        })
        .butNot("GET/user/123/x");

    new RoutePathAssert("GET", "user/:id")
        .matches("GET/user/xqi", (vars) -> {
          assertEquals("xqi", vars.get("id"));
        })
        .matches("GET/user/123", (vars) -> {
          assertEquals("123", vars.get("id"));
        })
        .butNot("GET/user/123/x");

    new RoutePathAssert("GET", "/:id")
        .matches("GET/xqi", (vars) -> {
          assertEquals("xqi", vars.get("id"));
        })
        .matches("GET/123", (vars) -> {
          assertEquals("123", vars.get("id"));
        })
        .butNot("GET/");

    new RoutePathAssert("GET", "/{id}")
        .matches("GET/xqi", (vars) -> {
          assertEquals("xqi", vars.get("id"));
        })
        .matches("GET/123", (vars) -> {
          assertEquals("123", vars.get("id"));
        })
        .butNot("GET/");
  }

  @Test
  public void varWithPrefix() {
    new RoutePathAssert("GET", "user/p{id}")
        .matches("GET/user/pxqi", (vars) -> {
          assertEquals("xqi", vars.get("id"));
        })
        .matches("GET/user/p123", (vars) -> {
          assertEquals("123", vars.get("id"));
        })
        .butNot("GET/user/p123/x");

    new RoutePathAssert("GET", "user/p:id")
        .matches("GET/user/pxqi", (vars) -> {
          assertEquals("xqi", vars.get("id"));
        })
        .matches("GET/user/p123", (vars) -> {
          assertEquals("123", vars.get("id"));
        })
        .butNot("GET/user/p123/x");
  }

  @Test
  public void regex() {
    new RoutePathAssert("GET", "user/{id:\\d+}")
        .matches("GET/user/123", (vars) -> {
          assertEquals("123", vars.get("id"));
        })
        .butNot("GET/user/123/x")
        .butNot("GET/user/123x")
        .butNot("GET/user/xqi");
  }

  @Test
  public void antExamples() {
    new RoutePathAssert("GET", "*.java")
        .matches("GET/.java")
        .matches("GET/x.java")
        .matches("GET/FooBar.java")
        .butNot("GET/FooBar.xml");

    new RoutePathAssert("GET", "?.java")
        .matches("GET/x.java")
        .matches("GET/A.java")
        .butNot("GET/.java")
        .butNot("GET/xyz.java");

    new RoutePathAssert("GET", "**/CVS/*")
        .matches("GET/CVS/Repository")
        .matches("GET/org/apache/CVS/Entries")
        .matches("GET/org/apache/jakarta/tools/ant/CVS/Entries")
        .butNot("GET/org/apache/CVS/foo/bar/Entries");

    new RoutePathAssert("GET", "org/apache/jakarta/**")
        .matches("GET/org/apache/jakarta/tools/ant/docs/index.html")
        .matches("GET/org/apache/jakarta/test.xml")
        .butNot("GET/org/apache/xyz.java");

    new RoutePathAssert("GET", "org/apache/**/CVS/*")
        .matches("GET/org/apache/CVS/Entries")
        .matches("GET/org/apache/jakarta/tools/ant/CVS/Entries")
        .butNot("GET/org/apache/CVS/foo/bar/Entries");
  }

  @Test
  public void moreExpression() {
    new RoutePathAssert("GET", "/views/products/**/*.cfm")
        .matches("GET/views/products/index.cfm")
        .matches("GET/views/products/SE10/index.cfm")
        .matches("GET/views/products/SE10/details.cfm")
        .matches("GET/views/products/ST80/index.cfm")
        .matches("GET/views/products/ST80/details.cfm")
        .butNot("GET/views/index.cfm")
        .butNot("GET/views/aboutUs/index.cfm")
        .butNot("GET/views/aboutUs/managementTeam.cfm");

    new RoutePathAssert("GET", "/views/index??.cfm")
        .matches("GET/views/index01.cfm")
        .matches("GET/views/index02.cfm")
        .matches("GET/views/indexAA.cfm")
        .butNot("GET/views/index01.htm")
        .butNot("GET/views/index1.cfm")
        .butNot("GET/views/indexOther.cfm")
        .butNot("GET/views/anotherDir/index01.cfm");
  }

  @Test
  public void normalizePath() {
    assertEquals("/", new RoutePattern("GET", "/").pattern());
    assertEquals("/", new RoutePattern("GET", "//").pattern());
    assertEquals("/foo", new RoutePattern("GET", "/foo//").pattern());
    assertEquals("/foo", new RoutePattern("GET", "foo//").pattern());
    assertEquals("/foo", new RoutePattern("GET", "foo").pattern());
    assertEquals("/foo", new RoutePattern("GET", "foo/").pattern());
    assertEquals("/foo/bar", new RoutePattern("GET", "/foo//bar").pattern());
  }

  @Test
  public void capturingGroups() {
    new RoutePathAssert("GET", "/js/*/2.1.3/*")
        .matches("GET/js/jquery/2.1.3/jquery.js", vars -> {
          assertEquals("jquery", vars.get(0));
          assertEquals("jquery.js", vars.get(1));
        });

    new RoutePathAssert("GET", "/js/**")
        .matches("GET/js/jquery/2.1.3/jquery.js", vars -> {
          assertEquals("jquery/2.1.3/jquery.js", vars.get(0));
        });

    new RoutePathAssert("GET", "/js/**/*.js")
        .matches("GET/js/jquery/2.1.3/jquery.js", vars -> {
          assertEquals("jquery/2.1.3/jquery", vars.get(0));
        });
  }

  @Test
  public void cornerCase() {
    new RoutePathAssert("GET", "/search/**")
        .matches("GET/search");

    new RoutePathAssert("GET", "/m/**")
        .butNot("GET/merge/login");
  }

  @Test
  public void ignoreCase() {
    new RoutePathAssert("GET", "/path/:id", true)
        .matches("GET/path/aB", vars -> {
          assertEquals("aB", vars.get(0));
        })
        .matches("GET/Path/ab", vars -> {
          assertEquals("ab", vars.get(0));
        });

    new RoutePathAssert("GET", "/path1", true)
        .matches("GET/path1")
        .matches("GET/Path1");
  }

  @Test
  public void shouldNotBreakOnMissing () {
    new RoutePathAssert("GET", "/")
        .butNot("GET(X11;");
  }
}
