/**
   * Get HttpResourceModel which matches the HttpMethod of the request.
   *
   * @param routableDestinations List of ResourceModels.
   * @param targetHttpMethod HttpMethod.
   * @param requestUri request URI.
   * @return RoutableDestination that matches httpMethod that needs to be handled. null if there are
   *     no matches.
   */
  private List<PatternPathRouterWithGroups.RoutableDestination<HttpResourceModel>>
      getMatchedDestination(
          List<PatternPathRouterWithGroups.RoutableDestination<HttpResourceModel>>
              routableDestinations,
          HttpMethod targetHttpMethod,
          String requestUri) {

    Iterable<String> requestUriParts = Splitter.on('/').omitEmptyStrings().split(requestUri);
    List<PatternPathRouterWithGroups.RoutableDestination<HttpResourceModel>> matchedDestinations =
        Lists.newArrayListWithExpectedSize(routableDestinations.size());
    int maxExactMatch = 0;
    int maxGroupMatch = 0;
    int maxPatternLength = 0;

    for (PatternPathRouterWithGroups.RoutableDestination<HttpResourceModel> destination :
        routableDestinations) {
      HttpResourceModel resourceModel = destination.getDestination();
      int groupMatch = destination.getGroupNameValues().size();

      for (HttpMethod httpMethod : resourceModel.getHttpMethod()) {
        if (targetHttpMethod.equals(httpMethod)) {

          int exactMatch =
              getExactPrefixMatchCount(
                  requestUriParts,
                  Splitter.on('/').omitEmptyStrings().split(resourceModel.getPath()));

          // When there are multiple matches present, the following precedence order is used -
          // 1. template path that has highest exact prefix match with the url is chosen.
          // 2. template path has the maximum groups is chosen.
          // 3. finally, template path that has the longest length is chosen.
          if (exactMatch > maxExactMatch) {
            maxExactMatch = exactMatch;
            maxGroupMatch = groupMatch;
            maxPatternLength = resourceModel.getPath().length();

            matchedDestinations.clear();
            matchedDestinations.add(destination);
          } else if (exactMatch == maxExactMatch && groupMatch >= maxGroupMatch) {
            if (groupMatch > maxGroupMatch || resourceModel.getPath().length() > maxPatternLength) {
              maxGroupMatch = groupMatch;
              maxPatternLength = resourceModel.getPath().length();
              matchedDestinations.clear();
            }
            matchedDestinations.add(destination);
          }
        }
      }
    }
    return matchedDestinations;
  }
  /**
   * Call the appropriate handler for handling the httprequest. 404 if path is not found. 405 if
   * path is found but httpMethod does not match what's configured.
   *
   * @param request instance of {@code HttpRequest}
   * @param responder instance of {@code HttpResponder} to handle the request.
   * @return HttpMethodInfo object, null if urlRewriter rewrite returns false, also when method
   *     cannot be invoked.
   * @throws HandlerException If URL rewriting fails
   */
  public HttpMethodInfoBuilder getDestinationMethod(HttpRequest request, HttpResponder responder)
      throws HandlerException {
    if (urlRewriter != null) {
      try {
        request.setUri(URI.create(request.getUri()).normalize().toString());
        if (!urlRewriter.rewrite(request, responder)) {
          return null;
        }
      } catch (Throwable t) {
        log.error("Exception thrown during rewriting of uri {}", request.getUri(), t);
        throw new HandlerException(
            HttpResponseStatus.INTERNAL_SERVER_ERROR,
            String.format("Caught exception processing request. Reason: %s", t.getMessage()));
      }
    }

    String acceptHeaderStr = request.headers().get(HttpHeaders.Names.ACCEPT);
    List<String> acceptHeader =
        (acceptHeaderStr != null)
            ? Arrays.asList(acceptHeaderStr.split("\\s*,\\s*"))
                .stream()
                .map(mediaType -> mediaType.split("\\s*;\\s*")[0])
                .collect(Collectors.toList())
            : null;

    String contentTypeHeaderStr = request.headers().get(HttpHeaders.Names.CONTENT_TYPE);
    // Trim specified charset since UTF-8 is assumed
    String contentTypeHeader =
        (contentTypeHeaderStr != null) ? contentTypeHeaderStr.split("\\s*;\\s*")[0] : null;

    try {
      String path = URI.create(request.getUri()).normalize().getPath();

      List<PatternPathRouterWithGroups.RoutableDestination<HttpResourceModel>>
          routableDestinations = patternRouter.getDestinations(path);

      List<PatternPathRouterWithGroups.RoutableDestination<HttpResourceModel>> matchedDestinations =
          getMatchedDestination(routableDestinations, request.getMethod(), path);

      if (!matchedDestinations.isEmpty()) {
        PatternPathRouterWithGroups.RoutableDestination<HttpResourceModel> matchedDestination =
            matchedDestinations
                .stream()
                .filter(
                    matchedDestination1 -> {
                      return matchedDestination1
                              .getDestination()
                              .matchConsumeMediaType(contentTypeHeader)
                          && matchedDestination1
                              .getDestination()
                              .matchProduceMediaType(acceptHeader);
                    })
                .findFirst()
                .get();
        HttpResourceModel httpResourceModel = matchedDestination.getDestination();

        // Call preCall method of handler interceptors.
        boolean terminated = false;
        ServiceMethodInfo serviceMethodInfo =
            new ServiceMethodInfo(
                httpResourceModel.getMethod().getDeclaringClass().getName(),
                httpResourceModel.getMethod());
        for (Interceptor interceptor : interceptors) {
          if (!interceptor.preCall(request, responder, serviceMethodInfo)) {
            // Terminate further request processing if preCall returns false.
            terminated = true;
            break;
          }
        }

        // Call httpresource handle method, return the HttpMethodInfo Object.
        if (!terminated) {
          // Wrap responder to make post hook calls.
          responder = new WrappedHttpResponder(responder, interceptors, request, serviceMethodInfo);
          return HttpMethodInfoBuilder.getInstance()
              .httpResourceModel(httpResourceModel)
              .httpRequest(request)
              .httpResponder(responder)
              .requestInfo(
                  matchedDestination.getGroupNameValues(), contentTypeHeader, acceptHeader);
        }
      } else if (!routableDestinations.isEmpty()) {
        // Found a matching resource but could not find the right HttpMethod so return 405
        throw new HandlerException(HttpResponseStatus.METHOD_NOT_ALLOWED, request.getUri());
      } else {
        throw new HandlerException(
            HttpResponseStatus.NOT_FOUND,
            String.format("Problem accessing: %s. Reason: Not Found", request.getUri()));
      }
    } catch (NoSuchElementException ex) {
      throw new HandlerException(
          HttpResponseStatus.UNSUPPORTED_MEDIA_TYPE,
          String.format("Problem accessing: %s. Reason: Unsupported Media Type", request.getUri()),
          ex);
    }
    return null;
  }