/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.jooby.internal;

import static java.util.Objects.requireNonNull;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import javaslang.Tuple;
import javaslang.Tuple4;

public class RoutePattern {

  private static final Pattern GLOB = Pattern
      /**            ?| **    | * | :var           | {var(:.*)} */
     //.compile("\\?|/\\*\\*|\\*|\\:((?:[^/]+)+?)              |\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");
      /**           ? | **:name            | * | :var           | */
      .compile("\\?|/\\*\\*(\\:(?:[^/]+))?|\\*|\\:((?:[^/]+)+?)|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");

  private static final Pattern SLASH = Pattern.compile("//+");

  private final Function<String, RouteMatcher> matcher;

  private String pattern;

  private List<String> vars;

  private List<String> reverse;

  private boolean glob;

  public RoutePattern(final String verb, final String pattern) {
    requireNonNull(verb, "A HTTP verb is required.");
    requireNonNull(pattern, "A path pattern is required.");
    this.pattern = normalize(pattern);
    Tuple4<Function<String, RouteMatcher>, List<String>, List<String>, Boolean> result = rewrite(this,
        verb.toUpperCase(), this.pattern.replace("/**/", "/**"));
    matcher = result._1;
    vars = result._2;
    reverse = result._3;
    glob = result._4;
  }

  public boolean glob() {
    return glob;
  }

  public List<String> vars() {
    return vars;
  }

  public String pattern() {
    return pattern;
  }

  public String reverse(final Map<String, Object> vars) {
    return reverse.stream()
        .map(segment -> vars.getOrDefault(segment, segment).toString())
        .collect(Collectors.joining(""));
  }

  public String reverse(final Object... value) {
    List<String> vars = vars();
    Map<String, Object> hash = new HashMap<>();
    for (int i = 0; i < Math.min(vars.size(), value.length); i++) {
      hash.put(vars.get(i), value[i]);
    }
    return reverse(hash);
  }

  public RouteMatcher matcher(final String path) {
    requireNonNull(path, "A path is required.");
    return matcher.apply(path);
  }

  private static Tuple4<Function<String, RouteMatcher>, List<String>, List<String>, Boolean> rewrite(
      final RoutePattern owner, final String verb, final String pattern) {
    List<String> vars = new LinkedList<>();
    String rwrverb = verbs(verb);
    StringBuilder patternBuilder = new StringBuilder(rwrverb);
    Matcher matcher = GLOB.matcher(pattern);
    int end = 0;
    boolean regex = !rwrverb.equals(verb);
    List<String> reverse = new ArrayList<>();
    boolean glob = false;
    while (matcher.find()) {
      String head = pattern.substring(end, matcher.start());
      patternBuilder.append(Pattern.quote(head));
      reverse.add(head);
      String match = matcher.group();
      if ("?".equals(match)) {
        patternBuilder.append("([^/])");
        reverse.add(match);
        regex = true;
        glob = true;
      } else if ("*".equals(match)) {
        patternBuilder.append("([^/]*)");
        reverse.add(match);
        regex = true;
        glob = true;
      } else if (match.equals("/**")) {
        reverse.add(match);
        patternBuilder.append("($|/.*)");
        regex = true;
        glob = true;
      } else if (match.startsWith("/**:")) {
        reverse.add(match.substring(1));
        String varName = match.substring(4);
        patternBuilder.append("/(?<v").append(vars.size()).append(">($|.*))");
        vars.add(varName);
        regex = true;
        glob = true;
      } else if (match.startsWith(":")) {
        regex = true;
        String varName = match.substring(1);
        patternBuilder.append("(?<v").append(vars.size()).append(">[^/]+)");
        vars.add(varName);
        reverse.add(varName);
      } else if (match.startsWith("{") && match.endsWith("}")) {
        regex = true;
        int colonIdx = match.indexOf(':');
        if (colonIdx == -1) {
          String varName = match.substring(1, match.length() - 1);
          patternBuilder.append("(?<v").append(vars.size()).append(">[^/]+)");
          vars.add(varName);
          reverse.add(varName);
        } else {
          String varName = match.substring(1, colonIdx);
          String regexpr = match.substring(colonIdx + 1, match.length() - 1);
          patternBuilder.append("(?<v").append(vars.size()).append(">");
          patternBuilder.append("**".equals(regexpr) ? "($|.*)" : regexpr);
          patternBuilder.append(')');
          vars.add(varName);
          reverse.add(varName);
        }
      }
      end = matcher.end();
    }
    String tail = pattern.substring(end, pattern.length());
    reverse.add(tail);
    patternBuilder.append(Pattern.quote(tail));
    return Tuple.of(fn(owner, regex, regex ? patternBuilder.toString() : verb + pattern, vars),
        vars, reverse, glob);
  }

  private static String verbs(final String verb) {
    String[] verbs = verb.split("\\|");
    if (verbs.length == 1) {
      return verb.equals("*") ? "(?:[^/]*)" : verb;
    }
    return "(?:" + verb + ")";
  }

  private static Function<String, RouteMatcher> fn(final RoutePattern owner, final boolean complex,
      final String pattern, final List<String> vars) {
    return new Function<String, RouteMatcher>() {
      final Pattern regex = complex ? Pattern.compile(pattern) : null;

      @Override
      public RouteMatcher apply(final String fullpath) {
        String path = fullpath.substring(fullpath.indexOf('/'));
        return complex
            ? new RegexRouteMatcher(path, regex.matcher(fullpath), vars)
            : new SimpleRouteMatcher(pattern, path, fullpath);
      }
    };
  }

  public static String normalize(final String pattern) {
    if (pattern.equals("*")) {
      return "/**";
    }
    if (pattern.equals("/")) {
      return "/";
    }
    String normalized = SLASH.matcher(pattern).replaceAll("/");
    if (normalized.equals("/")) {
      return "/";
    }
    StringBuilder buffer = new StringBuilder();
    if (!normalized.startsWith("/")) {
      buffer.append("/");
    }
    buffer.append(normalized);
    if (normalized.endsWith("/")) {
      buffer.setLength(buffer.length() - 1);
    }
    return buffer.toString();
  }

  @Override
  public String toString() {
    return pattern;
  }

}
