package net.sargue.mailgun;

import org.glassfish.jersey.media.multipart.BodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.file.FileDataBodyPart;
import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.Map;

/**
 * A mutable builder for a MIME multipart message. It has the capability
 * to handle attachments.
 * <p>
 * It is obtained from the main {@link MailBuilder} through the method
 * {@code multipart()}.
 */
@SuppressWarnings("unused")
public class MultipartBuilder {
    private static final String ATTACHMENT_NAME = "attachment";

    private final Configuration configuration;
    private FormDataMultiPart form = new FormDataMultiPart();

    MultipartBuilder(MailBuilder mailBuilder) {
        configuration = mailBuilder.configuration();

        MultivaluedMap<String, String> map = mailBuilder.form().asMap();
        for (Map.Entry<String, List<String>> entry : map.entrySet())
            for (String value : entry.getValue())
                form.field(entry.getKey(), value);
    }

    /**
     * Adds an attachment from a {@link File}.
     *
     * @param file a file to attach
     * @return this builder
     */
    public MultipartBuilder attachment(File file) {
        return bodyPart(new FileDataBodyPart(ATTACHMENT_NAME, file));
    }

    /**
     * Adds an attachment from a {@link InputStream}.
     *
     * @param is an stream to read the attachment
     * @return this builder
     */
    public MultipartBuilder attachment(InputStream is) {
        return bodyPart(new StreamDataBodyPart(ATTACHMENT_NAME, is));
    }

    /**
     * Adds a named attachment.
     *
     * @param is       an stream to read the attachment
     * @param filename the filename to give to the attachment
     * @return this builder
     */
    public MultipartBuilder attachment(InputStream is, String filename) {
        return bodyPart(new StreamDataBodyPart(ATTACHMENT_NAME, is, filename));
    }

    /**
     * Adds a named attachment with a custom MIME media type.
     *
     * @param is        an stream to read the attachment
     * @param filename  the filename to give to the attachment
     * @param mediaType the media type of the attachment
     * @return this builder
     */
    public MultipartBuilder attachment(InputStream is, String filename,
                                       MediaType mediaType) {
        return bodyPart(new StreamDataBodyPart(ATTACHMENT_NAME, is, filename,
                                               mediaType));
    }

    /**
     * Adds an attachment directly by content.
     *
     * @param content the content of the attachment
     * @param filename the filename of the attachment
     * @return this builder
     */
    public MultipartBuilder attachment(String content, String filename) {
        ByteArrayInputStream is = new ByteArrayInputStream(content.getBytes());
        return bodyPart(new StreamDataBodyPart(ATTACHMENT_NAME, is, filename));
    }

    /**
     * Adds a named inline attachment.
     *
     * @param is      an stream to read the attachment
     * @param cidName the name to give to the attachment as referenced by the HTML email body
     *                i.e. use cidName sample-image.png for the below example
     *                <p>
     *                    <img src="cid:sample-image.png" alt="sample">
     *                </p>
     * @return this builder
     */
    public MultipartBuilder inline(InputStream is, String cidName) {
        return bodyPart(new StreamDataBodyPart("inline", is, cidName));
    }

    /**
     * Finishes the building phase and returns a {@link Mail}.
     * <p>
     * This builder should not be used after invoking this method.
     *
     * @return a {@link Mail} built from this builder
     */
    public Mail build() {
        return new MailMultipart(configuration, form);
    }

    private MultipartBuilder bodyPart(BodyPart bodyPart) {
        form.bodyPart(bodyPart);
        return this;
    }
}