/*
 * 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.apache.ode.axis2;

import org.apache.commons.lang.StringUtils;
import org.apache.ode.utils.DOMUtils;
import org.apache.ode.utils.StreamUtils;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.AbstractHandler;
import org.mortbay.jetty.handler.ContextHandler;
import org.mortbay.jetty.handler.ContextHandlerCollection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

/**
 * @author <a href="mailto:midon@intalio.com">Alexis Midon</a>
 */
public class JettyWrapper {

    protected Server server;

    public JettyWrapper() throws Exception {
        this(7070);
    }

    public JettyWrapper(int port) throws Exception {
        server = new Server(port);
        addDefaultHandlers();
    }


    public JettyWrapper(int port, ContextHandler handler) {
        server = new Server(port);
        if(handler!=null) server.addHandler(handler);
        else addDefaultHandlers();
    }


    private void addDefaultHandlers() {
        ContextHandler arithmeticsContext = new ContextHandler();
        arithmeticsContext.setContextPath("/HttpBindingTest/ArithmeticsService");
        arithmeticsContext.setHandler(new ArithmeticsServiceHandler());

        ContextHandler blogContext = new ContextHandler();
        blogContext.setContextPath("/HttpBindingTest/BlogService");
        blogContext.setHandler(new BlogServiceHandler());

        ContextHandler echoContext = new ContextHandler();
        echoContext.setContextPath("/EchoService");
        echoContext.setHandler(new EchoServiceHandler());

        ContextHandlerCollection handlerColl = new ContextHandlerCollection();
        Handler[] handlers = {arithmeticsContext, blogContext, echoContext};
        handlerColl.setHandlers(handlers);

        server.addHandler(handlerColl);
    }

    public void start() throws Exception {
        try {
        	server.start();
        } catch (Exception e) {
        	server.stop();
        	server.start();
        }
    }

    public void stop() throws Exception {
        server.stop();
    }

    static public class EchoServiceHandler extends AbstractHandler {

        public void handle(String s, HttpServletRequest request, HttpServletResponse response, int i) throws IOException, ServletException {
            String method = request.getMethod();
            if (request.getParameter("ping")!=null) {
                response.setStatus(200);
                response.getOutputStream().println("Yep, I'm here!");
            } else {
                if (!"GET".equals(method) && !"POST".equals(method)) {
                    response.setStatus(405);
                    response.setHeader("Allow", "GET, POST");
                } else {
                    Enumeration h = request.getHeaderNames();
                    // send back all headers
                    while (h.hasMoreElements()) {
                        String hname = (String) h.nextElement();
                        Enumeration values = request.getHeaders(hname);
                        while (values.hasMoreElements()) {
                            String next = (String) values.nextElement();
                            response.addHeader(hname, next);
                        }
                    }

                    // send back the body if any
                    String body = new String(StreamUtils.read(request.getInputStream()));
                    if (StringUtils.isNotEmpty(body))
                        response.getOutputStream().println(body);

                }
            }


            ((Request) request).setHandled(true);
        }
    }

    private class ArithmeticsServiceHandler extends AbstractHandler {
        /*
        8 urls to handle:
        (GET)       http://localhost:8888/HttpBindingTestService/OlaElMundo-GET/plus/(left):(right)
        (GET)       http://         ........                    /OlaElMundo-GET/minus?left=&right=
        (DELETE)    http://localhost:8888/HttpBindingTestService/OlaElMundo-DELETE/plus/(left):(right)
        (DELETE)    http://         ........                    /OlaElMundo-DELETE/minus?left=&right=
        (POST)      http://         ........                    /OlaElMundo-POST/plus
        (POST)      http://         ........                    /OlaElMundo-POST/minus
        (PUT)       http://         ........                    /OlaElMundo-PUT/plus
        (PUT)       http://         ........                    /OlaElMundo-PUT/minus
        (POST)      http://         ........                    /SalutLaTerre/addition
        (POST)      http://         ........                    /SalutLaTerre/sumOfIntegers
        */
        public void handle(String s, HttpServletRequest request, HttpServletResponse response, int i) throws IOException, ServletException {

            try {
                String method = request.getMethod();
                String uri = request.getRequestURI();
                if ("/HttpBindingTestService/".equals(uri)) {
                    response.getWriter().println("HttpBindingTestService ready!");
                } else if (uri.contains("OlaElMundo-GET") || uri.contains("OlaElMundo-DELETE")) {
//                    if (!"GET".equalsIgnoreCase(method)) {
                    if (false) {
                        response.sendError(405, "Expecting method is GET");
                    } else {
                        if (!uri.contains("plus") && !uri.contains("minus")) {
                            response.sendError(404);
                        } else {
                            boolean ok = true;
                            int left = 0, right = 0;
                            try {
                                if (uri.contains("plus")) {
                                    int index = uri.lastIndexOf("/");
                                    String[] op = uri.substring(index + 1).split(":");
                                    left = Integer.parseInt(op[0]);
                                    right = Integer.parseInt(op[1]);
                                } else if (uri.contains("minus")) {
                                    left = Integer.parseInt(request.getParameter("left"));
                                    right = -1 * Integer.parseInt(request.getParameter("right"));
                                } else {
                                    ok = false;
                                }
                            } catch (NumberFormatException e) {
                                ok = false;
                            }

                            if (!ok) {
                                response.sendError(400);
                            } else {
                                Document doc = DOMUtils.newDocument();
                                Element resElt = doc.createElement("theresult");
                                resElt.setTextContent(String.valueOf(left + right));
                                response.getOutputStream().print(DOMUtils.domToString(resElt));
                                response.getOutputStream().close();
                                response.setStatus(200);
                            }
                        }
                    }
                } else if (uri.contains("OlaElMundo-POST") || uri.contains("OlaElMundo-PUT")) {
//                    if (!"POST".equalsIgnoreCase(method)) {
                    if (false) {
                        response.sendError(405, "Expecting method is POST");
                        return;
                    } else {
                        String operation;
                        if (!uri.contains("plus") && !uri.contains("minus")) {
                            response.sendError(404);
                        } else {
                            // parse body, form-urlencoded
                            int res = Integer.MIN_VALUE;
                            boolean ok = true;
                            StringBuffer sb = null;
                            String body = new String(StreamUtils.read(request.getInputStream()));
                            if (!body.matches("[^=]*=[+-]?\\d*&[^=]*=[+-]?\\d*")) {
                                ok = false;
                            } else {
                                String[] sp = body.split("&");
                                String[] op0 = sp[0].split("=");
                                String[] op1 = sp[1].split("=");
                                try {
                                    int left, right;
                                    if (op0[0].equals("left")) {
                                        left = Integer.valueOf(op0[1]);
                                        right = Integer.valueOf(op1[1]);
                                    } else {
                                        left = Integer.valueOf(op1[1]);
                                        right = Integer.valueOf(op0[1]);
                                    }
                                    if (uri.contains("minus")) {
                                        right = -1 * right;
                                    }
                                    res = left + right;
                                } catch (NumberFormatException e) {
                                    ok = false;
                                }
                            }
                            if (!ok) {
                                response.sendError(400);
                            } else {
                                Element resElt = DOMUtils.newDocument().createElement("theresult");
                                resElt.setTextContent(String.valueOf(res));
                                response.getOutputStream().print(DOMUtils.domToString(resElt));
                                response.setStatus(200);
                            }
                        }
                    }
                } else if (uri.contains("SalutLaTerre")) {
                    if (!"POST".equalsIgnoreCase(method)) {
                        response.sendError(405, "Expecting method is POST");
                        return;
                    } else {
                        if (uri.contains("addition")) {
                            Document bodyDoc = parseBody(request.getInputStream(), response);
                            if (bodyDoc != null) {
                                // we expect the element operandList
                                if (!"operandList".equals(bodyDoc.getDocumentElement().getNodeName())) {
                                    response.sendError(400, "The first element should be named operandList");
                                } else {
                                    Element firstOperand = DOMUtils.getFirstChildElement(bodyDoc.getDocumentElement());
                                    Element secondElement = DOMUtils.getNextSiblingElement(firstOperand);
                                    String left = DOMUtils.getTextContent(firstOperand);
                                    String right = DOMUtils.getTextContent(secondElement);
                                    Element res = bodyDoc.createElementNS("http://ode/bpel/test/arithmetics", "theresult");
                                    res.setTextContent(String.valueOf(Integer.valueOf(left) + Integer.valueOf(right)));
                                    response.getOutputStream().print(DOMUtils.domToString(res));
                                    response.setStatus(200);
                                }
                            }

                        } else if (uri.contains("sumOfIntegers")) {
                            Document bodyDoc = parseBody(request.getInputStream(), response);
                            if (bodyDoc != null) {
                                Element firstOperand = DOMUtils.getFirstChildElement(bodyDoc.getDocumentElement());
                                Element secondOperand = DOMUtils.getNextSiblingElement(firstOperand);
                                int left = Integer.valueOf(DOMUtils.getTextContent(firstOperand));
                                int right = Integer.valueOf(DOMUtils.getTextContent(secondOperand));

                                int min = Math.min(left, right);
                                int max = Math.max(left, right);
//                                Element arrayElt = bodyDoc.createElement("sumOfInteger");
                                Element anElt = bodyDoc.createElementNS("http://ode/bpel/test/arithmetics", "sumOfInteger");
                                Element msg = bodyDoc.createElement("theresult");
                                Element resultIs = bodyDoc.createElement("resultIs");
                                msg.setTextContent("A dummy message we don't care about. Only purpose is to have a complex type");
                                resultIs.setTextContent(String.valueOf((max * (max + 1) - min * (min + 1)) / 2));

                                anElt.appendChild(msg);
                                anElt.appendChild(resultIs);
                                response.getOutputStream().print(DOMUtils.domToString(anElt));
                                response.setStatus(200);
                            }
                        } else {
                            response.sendError(404);
                        }
                    }
                }
            } catch (Exception e) {
                response.sendError(500, e.getMessage());
            } finally {
                ((Request) request).setHandled(true);
            }
        }

        private Document parseBody(ServletInputStream bodyStream, HttpServletResponse response) throws IOException {
            if (bodyStream == null) {
                response.sendError(400, "Missing body!");
            } else {
                try {
                    return DOMUtils.parse(bodyStream);
                } catch (SAXException e) {
                    response.sendError(400, "Failed to parse body! " + e.getMessage());
                }
            }
            return null;
        }
    }

    private class BlogServiceHandler extends AbstractHandler {

        public void handle(String s, HttpServletRequest request, HttpServletResponse response, int i) throws IOException, ServletException {
            String method = request.getMethod();
            // actually we don't really care about this is.
            String articleId = s.substring(s.lastIndexOf("/") + 1);

            if ("GET".equalsIgnoreCase(method)) {
                doGet(request, response, articleId);
            } else if ("PUT".equalsIgnoreCase(method)) {
                doPut(request, response, articleId);
            } else if ("POST".equalsIgnoreCase(method)) {
                doPost(request, response, articleId);
            } else if ("DELETE".equalsIgnoreCase(method)) {
                doDelete(request, response, articleId);
            }
            ((Request) request).setHandled(true);
        }

        private void doGet(HttpServletRequest request, HttpServletResponse response, String articleId) throws IOException {
            String faultType = request.getHeader("Fault-Type");
            if ("400_not_found".equals(faultType)) {
                response.setStatus(400);
            } else if ("500_operation_with_no_fault_failed".equals(faultType)) {
                response.setStatus(500);
            } else if ("200_missing_part_in_header".equals(faultType)) {
                // a part is bound to a custom header
                // this test does not set it on purpose

                response.setContentType("text/xml");
                Element articleEl = createArticleDoc(articleId);

                response.getOutputStream().print(DOMUtils.domToString(articleEl));
                response.setStatus(200);
            } else if ("200_missing_body".equals(faultType)) {
                response.setHeader("TimestampHeader", request.getHeader("TimestampHeader"));
                response.setHeader("From", request.getHeader("From"));

                response.setStatus(200);
            } else if ("200_malformed_body".equals(faultType)) {
                // parts to http headers, just send them back and let the caller check the received values
                response.setHeader("TimestampHeader", request.getHeader("TimestampHeader"));
                response.setHeader("From", request.getHeader("From"));

                response.setContentType("text/xml");
                response.getOutputStream().print("<book><abstract>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</abstract>");
                response.setStatus(200);
            } else if ("202_empty_body".equals(faultType) || "204_empty_body".equals(faultType)) {
                response.setHeader("TimestampHeader", request.getHeader("TimestampHeader"));
                response.setHeader("From", request.getHeader("From"));

                response.setStatus(Integer.parseInt(faultType.substring(0, 3)));
            } else {
                // some parts are bound to http headers
                //  just send them back and let the caller check the received values
                response.setHeader("TimestampHeader", request.getHeader("TimestampHeader"));
                response.setHeader("From", request.getHeader("From"));

                response.setContentType("text/xml");
                Element articleEl = createArticleDoc(articleId);

                response.getOutputStream().print(DOMUtils.domToString(articleEl));
                response.setStatus(200);
            }
        }

        private Element createArticleDoc(String articleId) {
            Document doc = DOMUtils.newDocument();
            Element articleEl = doc.createElementNS("http://ode/bpel/test/blog", "article");
            Element idEl = doc.createElementNS("http://ode/bpel/test/blog", "id");
            Element titleEl = doc.createElementNS("http://ode/bpel/test/blog", "title");

            articleEl.appendChild(idEl);
            articleEl.appendChild(titleEl);

            idEl.setTextContent(articleId);
            titleEl.setTextContent("A title with a random number " + System.currentTimeMillis());
            return articleEl;
        }

        private void doPost(HttpServletRequest request, HttpServletResponse response, String articleId) {
            response.setHeader("Location", "http://examples.org/a_new_comment_on_article_" + articleId);
            response.setStatus(201); // Created
        }


        private void doPut(HttpServletRequest request, HttpServletResponse response, String articleId) throws IOException {
            String faultType = request.getHeader("Fault-Type");
            if ("500_no_body".equals(faultType)) {
                response.setStatus(500);
            } else if ("500_text_body".equals(faultType)) {
                response.setContentType("text");
                response.getOutputStream().print("Lorem ipsum dolor sit amet, consectetuer adipiscing elit.");
                response.setStatus(500);
            } else if ("500_malformed_xml_body".equals(faultType)) {
                response.setContentType("text/xml");
                response.getOutputStream().print("<book><abstract>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</abstract>");
                response.setStatus(500);
            } else if ("500_unknown_xml_body".equals(faultType)) {
                response.setContentType("text/xml");
                response.getOutputStream().print("<book><abstract>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</abstract></book>");
                response.setStatus(500);
            } else if ("500_expected_xml_body".equals(faultType)) {
                response.setContentType("text/xml");

                Document doc = DOMUtils.newDocument();
                Element faultEl = doc.createElementNS("http://ode/bpel/test/blog", "fault");
                Element timestamptEl = doc.createElementNS("http://ode/bpel/test/blog", "timestamp");
                Element detailsEl = doc.createElementNS("http://ode/bpel/test/blog", "details");

                faultEl.appendChild(timestamptEl);
                faultEl.appendChild(detailsEl);

                timestamptEl.setTextContent("" + System.currentTimeMillis());
                detailsEl.setTextContent("Fake fault with the expected xml body.");

                response.getOutputStream().print(DOMUtils.domToString(faultEl));
                response.setStatus(500);
            } else {
                response.setStatus(200);
            }
        }

        private void doDelete(HttpServletRequest request, HttpServletResponse response, String articleId) {
            response.setHeader("TimestampHeader", request.getHeader("TimestampHeader"));
            response.setHeader("User-Agent", request.getHeader("User-Agent"));
            response.setStatus(204); // No content
        }
    }

    public static void main(String[] args) {
        try {
            new JettyWrapper().server.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
