/*
 * Copyright (C) 2023 ctecinf.com.br
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package br.com.ctecinf;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 *
 * @author Cássio Conceição
 * @since 28/09/2023
 * @version 2309
 * @see http://ctecinf.com.br/
 */
public class Post {

    private static final String BR = "\r\n";
    private final String boundary;
    private final HttpURLConnection httpConn;
    private final OutputStream outputStream;
    private final PrintWriter writer;


    /**
     * This constructor initializes a new HTTP POST request with content type is
     * set to multipart/form-data
     *
     * @param serverURL
     * @param headers
     * @throws IOException
     */
    public Post(String serverURL, Map<String, String> headers) throws IOException {
        boundary = UUID.randomUUID().toString();
        httpConn = (HttpURLConnection) new URL(serverURL).openConnection();
        httpConn.setUseCaches(false);
        httpConn.setDoOutput(true);
        httpConn.setDoInput(true);
        httpConn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
        if (headers != null && !headers.isEmpty()) {
            for (String key : headers.keySet()) {
                String value = headers.get(key);
                httpConn.setRequestProperty(key, value);
            }
        }
        outputStream = httpConn.getOutputStream();
        writer = new PrintWriter(new OutputStreamWriter(outputStream, Charset.defaultCharset()), true);
    }

    /**
     * User Agent que fez a requisição
     *
     * @return String
     */
    public static final String getUserAgent() {
        return System.getProperty("java.vm.name") + "/" + System.getProperty("java.version") + " (" + System.getProperty("os.name") + "; " + System.getProperty("java.vendor") + "/" + System.getProperty("os.version") + "; " + System.getProperty("user.language") + ")";
    }

    /**
     * Abre conexão com o servidor para enviar dados
     *
     * @param serverURL
     * @return
     * @throws IOException
     */
    public static Post from(String serverURL) throws IOException {
        Map<String, String> headers = new HashMap<>();
        headers.put("User-Agent", Post.getUserAgent());
        return new Post(serverURL, headers);
    }

    /**
     * Adds a form field to the request
     *
     * @param name field name
     * @param value field value
     * @throws java.io.IOException
     */
    public void addData(String name, Object value) throws IOException {
        writer.append("--" + boundary).append(BR);
        if (value instanceof File) {
            File file = (File) value;
            writer.append("Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + file.getName() + "\"").append(BR);
            writer.append("Content-Type: " + URLConnection.guessContentTypeFromName(file.getName())).append(BR);
            writer.append("Content-Transfer-Encoding: binary").append(BR);
            writer.append(BR);
            writer.flush();
            try (FileInputStream inputStream = new FileInputStream(file)) {
                byte[] buffer = new byte[inputStream.available()];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
                outputStream.flush();
            }
            writer.append(BR);
        } else {
            writer.append("Content-Disposition: form-data; name=\"" + name + "\"").append(BR);
            writer.append("Content-Type: text/plain; charset=" + Charset.defaultCharset()).append(BR);
            writer.append(BR);
            writer.append(value == null ? "" : value.toString()).append(BR);
        }
        writer.flush();
    }

    /**
     * Completes the request and receives response from the server.
     *
     * @return String as response in case the server returned status OK,
     * otherwise an exception is thrown.
     * @throws IOException
     */
    public ByteArrayOutputStream finish() throws IOException {
        ByteArrayOutputStream result = new ByteArrayOutputStream();
        try (writer) {
            writer.flush();
            writer.append("--" + boundary + "--").append(BR);
        }
        if (httpConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
            byte[] buffer = new byte[httpConn.getInputStream().available()];
            int length;
            while ((length = httpConn.getInputStream().read(buffer)) != -1) {
                result.write(buffer, 0, length);
            }
            httpConn.disconnect();
        } else {
            throw new IOException("Server returned non-OK status: " + httpConn.getResponseCode());
        }
        return result;
    }
}
