package org.fdroid.fdroid.nearby.httpish;

import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.nearby.BluetoothConnection;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

public class Response {

    private static final String TAG = "bluetooth.Response";

    private final int statusCode;
    private final Map<String, String> headers;
    private final InputStream contentStream;

    public Response(int statusCode, Map<String, String> headers) {
        this(statusCode, headers, null);
    }

    /**
     * This class expects 'contentStream' to be open, and ready for use.
     * It will not close it either. However it will block while doing things
     * so you can call a method, wait for it to finish, and then close
     * it afterwards if you like.
     */
    public Response(int statusCode, Map<String, String> headers, InputStream contentStream) {
        this.statusCode = statusCode;
        this.headers = headers;
        this.contentStream = contentStream;
    }

    public Response(int statusCode, String mimeType, String content) {
        this.statusCode = statusCode;
        this.headers = new HashMap<>();
        this.headers.put("Content-Type", mimeType);
        this.contentStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
    }

    public Response(int statusCode, String mimeType, InputStream contentStream) {
        this.statusCode = statusCode;
        this.headers = new HashMap<>();
        this.headers.put("Content-Type", mimeType);
        this.contentStream = contentStream;
    }

    public void addHeader(String key, String value) {
        headers.put(key, value);
    }

    public int getStatusCode() {
        return statusCode;
    }

    public int getFileSize() {
        if (headers != null) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                if ("content-length".equals(entry.getKey().toLowerCase(Locale.ENGLISH))) {
                    try {
                        return Integer.parseInt(entry.getValue());
                    } catch (NumberFormatException e) {
                        return -1;
                    }
                }
            }
        }
        return -1;
    }

    /**
     * Extracts meaningful headers from the response into a more useful and safe
     * {@link FileDetails} object.
     */
    public FileDetails toFileDetails() {
        FileDetails details = new FileDetails();
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            Header.process(details, entry.getKey(), entry.getValue());
        }
        return details;
    }

    public InputStream toContentStream() throws UnsupportedOperationException {
        if (contentStream == null) {
            throw new UnsupportedOperationException("This kind of response doesn't have a content stream."
                    + " Did you perform a HEAD request instead of a GET request?");
        }
        return contentStream;
    }

    public void send(BluetoothConnection connection) throws IOException {

        Utils.debugLog(TAG, "Sending Bluetooth HTTP-ish response...");

        Writer output = new OutputStreamWriter(connection.getOutputStream());
        output.write("HTTP(ish)/0.1 200 OK\n");

        for (Map.Entry<String, String> entry : headers.entrySet()) {
            output.write(entry.getKey());
            output.write(": ");
            output.write(entry.getValue());
            output.write("\n");
        }

        output.write("\n");
        output.flush();

        if (contentStream != null) {
            Utils.copy(contentStream, connection.getOutputStream());
        }

        output.flush();
    }

    public static class Builder {

        private InputStream contentStream;
        private int statusCode = HttpURLConnection.HTTP_OK;
        private int fileSize = -1;
        private String etag;

        public Builder() {
        }

        public Builder(InputStream contentStream) {
            this.contentStream = contentStream;
        }

        public Builder setStatusCode(int statusCode) {
            this.statusCode = statusCode;
            return this;
        }

        public Builder setFileSize(int fileSize) {
            this.fileSize = fileSize;
            return this;
        }

        public Builder setETag(String etag) {
            this.etag = etag;
            return this;
        }

        public Response build() {

            Map<String, String> headers = new HashMap<>(3);

            if (fileSize > 0) {
                headers.put("Content-Length", Integer.toString(fileSize));
            }

            if (etag != null) {
                headers.put("ETag", etag);
            }

            return new Response(statusCode, headers, contentStream);
        }
    }
}
