package no.javabin.infrastructure.http.server;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import no.javabin.heroes.HttpRequestException;
import org.jsonbuddy.JsonNode;

public class ApiServlet extends HttpServlet {

    private static class ApiRoute {

        public ApiRoute(Object controller, Method action) {
            this.controller = controller;
            this.action = action;
        }

        private final Object controller;

        private final Method action;

        public Object getController() {
            return controller;
        }

        public Method getAction() {
            return action;
        }
    }


    private List<Object> controllers = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Map<String, String> pathParameters = new HashMap<>();
        ApiRoute route = findRoute(req, pathParameters);

        if (route != null) {
            Object[] arguments = createArguments(route.getAction(), req, pathParameters);
            Object result;
            try {
                result = invoke(route.getController(), route.getAction(), arguments);
            } catch (HttpRequestException e) {
                resp.sendError(e.getStatusCode(), e.getMessage());
                return;
            }
            sendResponse(result, resp);
        } else {
            resp.sendError(404);
        }
    }

    private void sendResponse(Object result, HttpServletResponse resp) throws IOException {
        if (result instanceof URL) {
            resp.sendRedirect(result.toString());
            return;
        }

        if (result instanceof JsonNode) {
            resp.setContentType("application/json");
        }
        resp.getWriter().write(result.toString());
    }

    private Object invoke(Object controller, Method action, Object[] arguments) {
        try {
            return action.invoke(controller, arguments);
        } catch (InvocationTargetException e) {
            if (e.getTargetException() instanceof RuntimeException) {
                throw (RuntimeException)e.getTargetException();
            } else {
                e.getTargetException().printStackTrace();
                throw new HttpRequestException(500, e.getTargetException());
            }
        } catch (IllegalAccessException e) {
            throw new HttpRequestException(500, e);
        }
    }

    private ApiRoute findRoute(HttpServletRequest req, Map<String, String> pathParameters) {
        for (Object controller : controllers) {
            for (Method method : controller.getClass().getMethods()) {
                pathParameters.clear();
                Get annotation = method.getAnnotation(Get.class);
                if (pathMatches(annotation.value(), req.getPathInfo(), pathParameters)) {
                    return new ApiRoute(controller,  method);
                }
            }
        }
        return null;
    }

    public boolean pathMatches(String actionPathPattern, String actualPath, Map<String, String> pathParameters) {
        String[] patternParts = actionPathPattern.split("/");
        String[] actualParts = actualPath.split("/");
        if (patternParts.length != actualParts.length) return false;

        for (int i = 0; i < patternParts.length; i++) {
            if (patternParts[i].startsWith(":")) {
                pathParameters.put(patternParts[i].substring(1), actualParts[i]);
            } else if (!patternParts[i].equals(actualParts[i])) {
                return false;
            }
        }

        return true;
    }


    public Object[] createArguments(Method method, HttpServletRequest req, Map<String, String> pathParameters) {
        Object[] arguments = new Object[method.getParameterCount()];
        for (int i = 0; i < arguments.length; i++) {
            Parameter parameter = method.getParameters()[i];
            if (parameter.getType() == HttpSession.class) {
                arguments[i] = req.getSession();
                continue;
            }
            if (parameter.getType() == HttpServletRequest.class) {
                arguments[i] = req;
                continue;
            }

            PathParam pathParam;
            RequestParam reqParam;
            SessionParameter sessionParam;
            if ((pathParam = parameter.getAnnotation(PathParam.class)) != null) {
                arguments[i] = pathParameters.get(pathParam.value());
            } else if ((sessionParam = parameter.getAnnotation(SessionParameter.class)) != null) {
                arguments[i] = req.getSession().getAttribute(sessionParam.value());
            } else if ((reqParam = parameter.getAnnotation(RequestParam.class)) != null) {
                arguments[i] = req.getParameter(reqParam.value());
            } else {
                throw new IllegalArgumentException("Don't know how to get "
                        + method.getDeclaringClass().getSimpleName() + "#" + method.getName()
                        + " parameter " + i + ": of type " + parameter.getType().getSimpleName() + " " + Arrays.asList(parameter.getAnnotations()));
            }
        }
        return arguments;
    }


    protected void registerController(Object controller) {
        this.controllers.add(controller);
    }


}

@@ -3246,16 +3246,38 @@
     if (
+annotation != null && 
 pathMatc
@@ -4340,32 +4340,263 @@
 gth; i++) %7B%0A    
+        arguments%5Bi%5D = createArgument(method, i, req, pathParameters);%0A        %7D%0A        return arguments;%0A    %7D%0A%0A    public Object createArgument(Method method, int i, HttpServletRequest req, Map%3CString, String%3E pathParameters) %7B%0A
         Paramete
@@ -4636,20 +4636,16 @@
 s()%5Bi%5D;%0A
-    
         
@@ -4704,34 +4704,22 @@
         
-    arguments%5Bi%5D =
+return
  req.get
@@ -4741,51 +4741,14 @@
     
-        continue;%0A            %7D%0A           
+%7D else
  if 
@@ -4811,34 +4811,22 @@
         
-    arguments%5Bi%5D =
+return
  req;%0A  
@@ -4835,40 +4835,16 @@
     
-  
-      continue;%0A            %7D%0A
+%7D else %7B
 %0A   
@@ -5049,30 +5049,22 @@
         
-arguments%5Bi%5D =
+return
  pathPar
@@ -5206,38 +5206,30 @@
             
-arguments%5Bi%5D =
+return
  req.getSess
@@ -5377,30 +5377,22 @@
         
-arguments%5Bi%5D =
+return
  req.get
@@ -5788,42 +5788,15 @@
   %7D%0A
-        return arguments;%0A
     %7D%0A%0A
-%0A
     
@@ -5899,11 +5899,10 @@
 %0A    %7D%0A%0A
-%0A
 %7D%0A

Refactor ApiServlet