package org.rakam.server.http;

import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.util.AttributeKey;

import java.util.AbstractMap;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static io.netty.handler.codec.http.HttpHeaders.Names.ACCESS_CONTROL_ALLOW_HEADERS;
import static io.netty.handler.codec.http.HttpHeaders.Names.ACCESS_CONTROL_ALLOW_METHODS;
import static io.netty.handler.codec.http.HttpHeaders.Names.ACCESS_CONTROL_EXPOSE_HEADERS;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

public class RouteMatcher
{
    private HashMap<PatternBinding, HttpRequestHandler> routes = new HashMap();
    private HttpRequestHandler noMatch = request -> request.response("404", HttpResponseStatus.NOT_FOUND).end();
    static final AttributeKey<String> PATH = AttributeKey.valueOf("/path");
    private List<Map.Entry<PatternBinding, HttpRequestHandler>> prefixRoutes = new LinkedList<>();

    public void handle(ChannelHandlerContext ctx, WebSocketFrame frame)
    {
        String path = ctx.attr(PATH).get();
        final Object handler = routes.get(new PatternBinding(HttpMethod.GET, path));
        if (handler != null) {
            if (handler instanceof WebSocketService) {
                ((WebSocketService) handler).handle(ctx, frame);
            }
        }
        else {
            // TODO: WHAT TO DO?
            ctx.close();
        }
    }

    public void handle(RakamHttpRequest request)
    {
        String path = cleanPath(request.path());
        int lastIndex = path.length() - 1;
        if (lastIndex > 0 && path.charAt(lastIndex) == '/') {
            path = path.substring(0, lastIndex);
        }

        final HttpRequestHandler handler = routes.get(new PatternBinding(request.getMethod(), path));
        if (handler != null) {
            if (handler instanceof WebSocketService) {
                request.context().attr(PATH).set(path);
            }
            handler.handle(request);
        }
        else {
            for (Map.Entry<PatternBinding, HttpRequestHandler> prefixRoute : prefixRoutes) {
                if (path.startsWith(prefixRoute.getKey().pattern)) {
                    prefixRoute.getValue().handle(request);
                    return;
                }
            }
            noMatch.handle(request);
        }
    }

    private String cleanPath(String path)
    {
        StringBuilder builder = new StringBuilder();
        boolean edge = false;
        int length = path.length();
        for (int i = 0; i < length; i++) {
            char c = path.charAt(i);
            if (c == '/') {
                if (!edge) {
                    builder.append(c);
                }
                edge = true;
            }
            else {
                builder.append(c);
                edge = false;
            }
        }
        return builder.toString();
    }

    public void add(String path, WebSocketService handler)
    {
        PatternBinding key = new PatternBinding(HttpMethod.GET, path);
        routes.put(key, handler);
    }

    public void add(HttpMethod method, String path, HttpRequestHandler handler)
    {
        if (path.endsWith("*")) {
            String substring = path.substring(0, path.length() - 1);
            routes.put(new PatternBinding(method, substring), handler);
            prefixRoutes.add(new AbstractMap.SimpleImmutableEntry<>(new PatternBinding(method, substring), handler));
        }
        else {
            if (path.length() > 1 && path.endsWith("/")) {
                path = path.substring(0, path.length() - 1);
            }
            routes.put(new PatternBinding(method, path), handler);
        }
    }

    public void noMatch(HttpRequestHandler handler)
    {
        noMatch = handler;
    }

    public static class PatternBinding
    {
        final HttpMethod method;
        final String pattern;

        private PatternBinding(HttpMethod method, String pattern)
        {
            this.method = method;
            this.pattern = pattern;
        }

        @Override
        public boolean equals(Object o)
        {
            if (this == o) {
                return true;
            }
            if (!(o instanceof PatternBinding)) {
                return false;
            }

            PatternBinding that = (PatternBinding) o;

            if (!method.equals(that.method)) {
                return false;
            }
            if (!pattern.equals(that.pattern)) {
                return false;
            }

            return true;
        }

        @Override
        public int hashCode()
        {
            int result = method.hashCode();
            result = 31 * result + pattern.hashCode();
            return result;
        }
    }

    public static class MicroRouteMatcher
    {
        private final RouteMatcher routeMatcher;
        private String path;

        public MicroRouteMatcher(RouteMatcher routeMatcher, String path)
        {
            this.routeMatcher = routeMatcher;
            this.path = path;
        }

        public void add(String lastPath, HttpMethod method, HttpRequestHandler handler)
        {
            Objects.requireNonNull(path, "path is not configured");
            routeMatcher.add(method, path.equals("/") ? lastPath : path + lastPath, handler);
        }
    }
}
@@ -2003,32 +2003,81 @@
 ex);%0A        %7D%0A%0A
+        HttpMethod method = request.getMethod();%0A
         final Ht
@@ -2133,35 +2133,22 @@
 Binding(
-request.getM
+m
 ethod
-()
 , path))
@@ -2476,24 +2476,70 @@
         if (
+method.equals(prefixRoute.getKey().method) && 
 path.startsW

fix bug for prefix route matcher