package valandur.webapi.servlet;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import valandur.webapi.permission.Permission;
import valandur.webapi.WebAPI;
import valandur.webapi.misc.TreeNode;
import valandur.webapi.permission.Permissions;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public abstract class WebAPIServlet extends HttpServlet {

    private void handleVerb(String verb, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json; charset=utf-8");

        try {
            Method method = this.getClass().getDeclaredMethod("handle" + verb, ServletData.class);
            if (method.isAnnotationPresent(Permission.class)) {
                String[] reqPerms = method.getAnnotation(Permission.class).perm().split("\\.");
                TreeNode<String, Boolean> permissions = (TreeNode<String, Boolean>)req.getAttribute("perms");

                if (permissions == null) {
                    WebAPI.getInstance().getLogger().warn(req.getRemoteAddr() + " does not have permisson to access " + req.getRequestURI());
                    resp.sendError(HttpServletResponse.SC_FORBIDDEN);
                    return;
                }

                TreeNode<String, Boolean> methodPerms = Permissions.subPermissions(permissions, reqPerms);
                if (!methodPerms.getValue()) {
                    WebAPI.getInstance().getLogger().warn(req.getRemoteAddr() + " does not have permission to access " + req.getRequestURI());
                    resp.sendError(HttpServletResponse.SC_FORBIDDEN);
                    return;
                }

                req.setAttribute("dataPerms", methodPerms);
            } else {
                WebAPI.getInstance().getLogger().warn(verb + " in " + this.getClass().getName() + " is not annotated with @Permission");
                resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                return;
            }

            if (verb.equalsIgnoreCase("Post") || verb.equalsIgnoreCase("Put")) {
                try {
                    ObjectMapper mapper = new ObjectMapper();
                    JsonNode node = mapper.readTree(req.getReader());
                    req.setAttribute("body", node);
                } catch (Exception e) {
                    resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
                    return;
                }
            }

            ServletData data = new ServletData(req, resp);
            method.invoke(this, data);
            PrintWriter out = data.getWriter();

            ObjectMapper om = new ObjectMapper();
            if (!data.isErrorSent()) {
                resp.setStatus(HttpServletResponse.SC_OK);
                out.write(om.writeValueAsString(data.getNode()));
            }
        } catch (NoSuchMethodException e) {
            // Method does not exist (endpoint/verb not supported)
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
        } catch (InvocationTargetException | IllegalAccessException e) {
            // Error executing the method
            e.printStackTrace();
            resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }

    @Override
    protected final void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.handleVerb("Get", req, resp);
    }

    @Override
    protected final void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.handleVerb("Post", req, resp);
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.handleVerb("Put", req, resp);
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.handleVerb("Delete", req, resp);
    }

    protected void handleGet(ServletData data) {
        data.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "Not implemented");
    }

    protected void handlePost(ServletData data) {
        data.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "Not implemented");
    }

    protected void handlePut(ServletData data) {
        data.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "Not implemented");
    }

    protected void handleDelete(ServletData data) {
        data.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "Not implemented");
    }
}

@@ -805,32 +805,456 @@
  IOException %7B%0D%0A
+        resp.addHeader(%22Access-Control-Allow-Origin%22,%22*%22);%0D%0A        resp.addHeader(%22Access-Control-Allow-Methods%22,%22GET,PUT,POST,DELETE%22);%0D%0A        resp.addHeader(%22Access-Control-Allow-Headers%22,%22Origin, X-Requested-With, Content-Type, Accept%22);%0D%0A%0D%0A        // Return early if OPTIONS%0D%0A        if (req.getMethod().equals(%22OPTIONS%22) ) %7B%0D%0A            resp.setStatus(HttpServletResponse.SC_OK);%0D%0A            return;%0D%0A        %7D%0D%0A%0D%0A
         resp.set
@@ -4786,32 +4786,223 @@
 esp);%0D%0A    %7D%0D%0A%0D%0A
+    @Override%0D%0A    protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException %7B%0D%0A        this.handleVerb(%22Options%22, req, resp);%0D%0A    %7D%0D%0A%0D%0A
     protected vo

Fix options requests not being handled properly, and add CORS headers to all requests