/**
 * 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.netbeans.html.presenters.browser;

import java.io.IOException;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import static org.testng.Assert.*;
import org.testng.annotations.Test;

public class SimpleServerTest {
    public SimpleServerTest() {
    }

    @Test(dataProviderClass = ServerFactories.class, dataProvider = "serverFactories")
    public void testConnectionToTheServer(String name, Supplier<HttpServer<?,?,?,?>> serverProvider) throws IOException {
        if (serverProvider == null) {
            return;
        }
        int min = 42343;
        int max = 49343;
        HttpServer<?, ?, ?, ?> server = serverProvider.get();
        server.init(min, max);
        server.addHttpHandler(new HttpServer.Handler() {
            @Override
            <Request, Response> void service(HttpServer<Request, Response, ?, ?> server, Request rqst, Response rspns) throws IOException {
                assertEquals(server.getServerName(rqst), "localhost", "Connecting from localhost");
                assertEquals(server.getServerPort(rqst), server.getPort(), "Connecting via local port");
                assertEquals(server.getMethod(rqst), "GET", "Requesting GET");

                server.setCharacterEncoding(rspns, "UTF-8");
                server.setContentType(rspns, "text/x-test");
                Browser.cors(server, rspns);
                try (Writer w = server.getWriter(rspns)) {
                    final String n = server.getParameter(rqst, "name");
                    final String reply;
                    switch (server.getRequestURI(rqst)) {
                        case "/reply/hi": reply = "Ahoj " + n + "!"; break;
                        case "/reply/tchus": reply = "Ciao " + n + "!"; break;
                        default: reply = "What?";
                    }
                    w.write(reply);
                }
            }
        }, "/reply");
        server.start();

        int realPort = server.getPort();
        assertTrue(realPort <= max && realPort >= min, "Port from range (" + min + ", " + max + ") selected: " + realPort);

        final String baseUri = "http://localhost:" + realPort;
        assertURL("Ahoj John!", baseUri, "/reply/hi?name=John");
        assertURL("Ciao John!", baseUri, "/reply/tchus?name=John");

        server.shutdownNow();

    }

    private static void assertURL(String msg, String baseUri, final String path) throws IOException, MalformedURLException {
        URL url = new URL(baseUri + path);
        URLConnection conn = url.openConnection();

        final String contentAndAttribs = conn.getContentType();
        assertNotNull(contentAndAttribs, "Content-Type specified");
        int semicolon = contentAndAttribs.indexOf(';');
        final String content = semicolon == -1 ? contentAndAttribs : contentAndAttribs.substring(0, semicolon);
        assertEquals(content, "text/x-test");

        byte[] arr = new byte[8192];
        int len = conn.getInputStream().read(arr);
        assertNotEquals(len, -1, "Something shall be read");

        String txt = new String(arr, 0, len, StandardCharsets.UTF_8);
        assertEquals(txt, msg, "Message from the handler delivered");

        assertEquals(conn.getHeaderField("Access-Control-Allow-Origin"), "*");
    }

    @Test(dataProviderClass = ServerFactories.class, dataProvider = "serverFactories")
    public void testHeadersAndBody(String name, Supplier<HttpServer<?,?,?,?>> serverProvider) throws IOException {
        if (serverProvider == null) {
            return;
        }
        int min = 42343;
        int max = 49343;
        HttpServer<?, ?, ?, ?> server = serverProvider.get();
        server.init(min, max);
        server.addHttpHandler(new HttpServer.Handler() {
            @Override
            <Request, Response> void service(HttpServer<Request, Response, ?, ?> server, Request rqst, Response rspns) throws IOException {
                StringBuilder sb = new StringBuilder(server.getBody(rqst));

                server.setCharacterEncoding(rspns, "UTF-8");
                server.setContentType(rspns, "text/plain");
                try (Writer w = server.getWriter(rspns)) {
                    final String action = server.getHeader(rqst, "action");
                    assertNotNull(action, "action is specified");
                    String reply;
                    switch (action) {
                        case "reverse": reply = sb.reverse().toString(); break;
                        case "upper": reply = sb.toString().toUpperCase(); break;
                        default: reply = "What?";
                    }
                    w.write(reply);
                }
            }
        }, "/action");
        server.start();

        int realPort = server.getPort();
        assertTrue(realPort <= max && realPort >= min, "Port from range (" + min + ", " + max + ") selected: " + realPort);

        final String baseUri = "http://localhost:" + realPort;
        assertReadURL("reverse", "Ahoj", baseUri, "johA");
        assertReadURL("upper", "Ahoj", baseUri, "AHOJ");

        server.shutdownNow();

    }

    private static void assertReadURL(String action, String data, String baseUri, final String exp) throws IOException, MalformedURLException {
        URL url = new URL(baseUri + "/action");
        URLConnection conn = url.openConnection();
        conn.addRequestProperty("action", action);
        conn.setDoOutput(true);
        conn.connect();
        conn.getOutputStream().write(data.getBytes());


        final String contentAndAttribs = conn.getContentType();
        assertNotNull(contentAndAttribs, "Content-Type specified");
        int semicolon = contentAndAttribs.indexOf(';');
        final String content = semicolon == -1 ? contentAndAttribs : contentAndAttribs.substring(0, semicolon);
        assertEquals(content, "text/plain");

        byte[] arr = new byte[8192 * 8];
        int offset = 0;
        for (;;) {
            int len = conn.getInputStream().read(arr, offset, arr.length - offset);
            if (len == -1) {
                break;
            }
            offset += len;
        }
        assertNotEquals(offset, 0, "Something shall be read");

        String txt = new String(arr, 0, offset, StandardCharsets.UTF_8);
        assertEquals(txt, exp, "Message from the handler delivered");
    }

    @Test(dataProviderClass = ServerFactories.class, dataProvider = "serverFactories")
    public void testWaitForData(String name, Supplier<HttpServer<?,?,?,?>> serverProvider) throws IOException {
        if (serverProvider == null) {
            return;
        }
        ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
        int min = 32343;
        int max = 33343;
        HttpServer<?, ?, ?, ?> server = serverProvider.get();
        server.init(min, max);

        class HandlerImpl extends HttpServer.Handler {
            @Override
            <Request, Response> void service(HttpServer<Request, Response, ?, ?> server, Request rqst, Response rspns) throws IOException {
                server.setCharacterEncoding(rspns, "UTF-8");
                server.setContentType(rspns, "text/x-test");
                Browser.cors(server, rspns);
                server.suspend(rspns);
                exec.schedule((Callable <Void>) () -> {
                    server.resume(rspns, () -> {
                        Writer w = server.getWriter(rspns);
                        try {
                            w.write("Finished!");
                        } catch (IOException ex) {
                            throw new IllegalStateException(ex);
                        }
                    });
                    return null;
                }, 1, TimeUnit.SECONDS);
            }
        }
        server.addHttpHandler(new HandlerImpl(), "/async");
        server.start();

        int realPort = server.getPort();
        assertTrue(realPort <= max && realPort >= min, "Port from range (" + min + ", " + max + ") selected: " + realPort);

        final String baseUri = "http://localhost:" + realPort;
        assertURL("Finished!", baseUri, "/async");

        exec.shutdown();
        server.shutdownNow();
    }

    @Test(dataProviderClass = ServerFactories.class, dataProvider = "serverFactories")
    public void testEnormousBody(String name, Supplier<HttpServer<?,?,?, ?>> serverProvider) throws IOException {
        if (serverProvider == null) {
            return;
        }
        ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
        int min = 32343;
        int max = 33343;
        HttpServer<?, ?, ?, ?> server = serverProvider.get();
        server.init(min, max);

        String id = veryLongId();

        class HandlerImpl extends HttpServer.Handler {
            @Override
            <Request, Response> void service(HttpServer<Request, Response, ?, ?> server, Request rqst, Response rspns) throws IOException {
                server.setCharacterEncoding(rspns, "UTF-8");
                server.setContentType(rspns, "text/plain");
                Browser.cors(server, rspns);

                assertEquals("lower", server.getHeader(rqst, "action"));

                String gotId = server.getBody(rqst);
                if (!gotId.equals(id)) {
                    fail("Id as expected by " + server + " isn't the same " + id.length() + " != " + gotId.length());
                }
                server.getWriter(rspns).write(gotId.toLowerCase());
            }
        }
        server.addHttpHandler(new HandlerImpl(), "/action");
        server.start();

        int realPort = server.getPort();
        assertTrue(realPort <= max && realPort >= min, "Port from range (" + min + ", " + max + ") selected: " + realPort);

        final String baseUri = "http://localhost:" + realPort;
        assertReadURL("lower", id, baseUri, id.toLowerCase());

        exec.shutdown();
        server.shutdownNow();
    }

    private static String veryLongId() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 10000; i++) {
            final int max = 'Z' - 'A';
            int ch = 'A' + (i % max);
            sb.append((char) ch);
        }
        return sb.toString();
    }
}
