package net.zephyrizing.http_server;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import net.zephyrizing.http_server.HttpRequest;
import net.zephyrizing.http_server.HttpRequest.Method;
import net.zephyrizing.http_server.HttpResponse;
import net.zephyrizing.http_server.exceptions.HttpServerException;

import static net.zephyrizing.http_server.HttpRequest.Method.*;

public class HttpProtocol {

    public static final int MAJOR_VERSION = 1;
    public static final int MINOR_VERSION = 1;

    public static final Collector<CharSequence, ?, String> collectAsHttpHeader = Collectors.joining("\r\n", "", "\r\n\r\n");

    public static final Pattern HEADER_RE = Pattern.compile("^([^\\s:]+):\\s*(.*?)\\s*$");

    public static HttpRequest requestFromInputStream(InputStream stream)
        throws HttpServerException {

        HttpInputStream r =
            new HttpInputStream(
                new BufferedInputStream(stream));
        RequestBuilder b = new RequestBuilder();

        try {
            String s = r.readLine();
            if (s == null) {
                return null;
            }
            parseRequestLine(b, s);

            List<String> headerLines = new ArrayList<String>();
            s = r.readLine();
            while (s != null) {
                headerLines.add(s);
                s = r.readLine();
            }
            parseRequestHeaders(b, headerLines);

            if (b.hasContentHeader()) {
                ByteBuffer body = r.readBody(b.contentLength());
                b.body(body);
            }
        } catch (IOException e) {
            return null;
        } catch (IllegalArgumentException e) {
            return null;
        }

        return b.build();
    }

    public static InputStream responseStream(HttpResponse response) {
        String headStr = Stream.concat(response.getStatusLineStream(),
                                       response.getHeaderStream())
            .collect(collectAsHttpHeader);

        InputStream head = new ByteArrayInputStream(headStr.getBytes());
        InputStream body = response.getData();

        return new SequenceInputStream(head, body);
    }

    private static void parseRequestLine(RequestBuilder b, String requestLine) {
        String[] methodPathProto = requestLine.split(" ");

        if (methodPathProto.length != 3) {
            throw new IllegalArgumentException();
        }

        b.method(Method.valueOf(methodPathProto[0]))
            .path(methodPathProto[1]);
    }

    private static void parseRequestHeaders(RequestBuilder b, List<String> headerLines) {
        headerLines.forEach(s -> parseRequestHeader(b, s));
    }

    private static void parseRequestHeader(RequestBuilder b,  String headerLine) {
        Matcher m = HEADER_RE.matcher(headerLine);
        if (m.matches()) {
            String headerKey = m.group(1);
            String headerValueStr = m.group(2);

            // This shouldn't really be done here. Header splitting isn't quite this generic
            String headerValues[] = headerValueStr.split("\\s*,\\s*");
            b.header(headerKey, Arrays.asList(headerValues));
        }
    }
}

@@ -1227,17 +1227,19 @@
 tStream 
-s
+inS
 tream)%0A 
@@ -1299,17 +1299,22 @@
 tStream 
-r
+stream
  =%0A     
@@ -1381,17 +1381,19 @@
 tStream(
-s
+inS
 tream));
@@ -1417,16 +1417,22 @@
 uilder b
+uilder
  = new R
@@ -1482,21 +1482,32 @@
  String 
-s = r
+tempStr = stream
 .readLin
@@ -1527,17 +1527,23 @@
     if (
-s
+tempStr
  == null
@@ -1619,19 +1619,31 @@
 stLine(b
-, s
+uilder, tempStr
 );%0A%0A    
@@ -1706,37 +1706,48 @@
 ();%0A            
-s = r
+tempStr = stream
 .readLine();%0A   
@@ -1762,17 +1762,23 @@
  while (
-s
+tempStr
  != null
@@ -1813,17 +1813,23 @@
 nes.add(
-s
+tempStr
 );%0A     
@@ -1843,13 +1843,24 @@
     
-s = r
+tempStr = stream
 .rea
@@ -1915,16 +1915,22 @@
 eaders(b
+uilder
 , header
@@ -1955,16 +1955,22 @@
    if (b
+uilder
 .hasCont
@@ -2018,17 +2018,22 @@
  body = 
-r
+stream
 .readBod
@@ -2035,16 +2035,22 @@
 adBody(b
+uilder
 .content
@@ -2077,16 +2077,22 @@
        b
+uilder
 .body(bo
@@ -2268,16 +2268,22 @@
 return b
+uilder
 .build()
@@ -2780,16 +2780,22 @@
 uilder b
+uilder
 , String
@@ -2983,16 +2983,22 @@
        b
+uilder
 .method(
@@ -3139,16 +3139,22 @@
 uilder b
+uilder
 , List%3CS
@@ -3228,16 +3228,22 @@
 Header(b
+uilder
 , s));%0A 
@@ -3307,16 +3307,22 @@
 uilder b
+uilder
 ,  Strin
@@ -3684,16 +3684,22 @@
        b
+uilder
 .header(

Use more readable variable names in HttpProtocol