package org.subethamail.smtp.client;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashSet;
import java.util.Set;

/**
 * PlainAuthenticator implements the SASL PLAIN mechanism which authenticates
 * the client using a name - password combination.
 * 
 * @see <a href="http://tools.ietf.org/html/rfc4616">RFC 4616: The PLAIN Simple
 *      Authentication and Security Layer (SASL) Mechanism</a>
 */
public final class PlainAuthenticator implements Authenticator {
    private final String user;
    private final String password;
    private final SmartClient smartClient;

    public PlainAuthenticator(SmartClient smartClient, String user, String password) {
        this.smartClient = smartClient;
        this.user = user;
        this.password = password;
    }

    @Override
    public void authenticate() throws SMTPException, AuthenticationNotSupportedException, IOException {
        checkAuthPlainSupport();

        String initialClientResponse = constructInitialClientResponse();
        smartClient.sendAndCheck("AUTH PLAIN " + initialClientResponse);
    }

    /**
     * Checks if the server supports this mechanism.
     * 
     * @throws AuthenticationNotSupportedException
     *             if the server does not support this mechanism or
     *             authentication at all.
     */
    private void checkAuthPlainSupport() throws AuthenticationNotSupportedException {
        String mechanismsString = smartClient.getExtensions().get("AUTH");
        if (mechanismsString == null) {
            throw new AuthenticationNotSupportedException("Cannot authenticate, because the AUTH extension is "
                    + "not supported by the server. Maybe the server expects " + "TLS first");
        }
        Set<String> mechanisms = parseMechanismsList(mechanismsString);
        if (!mechanisms.contains("PLAIN")) {
            throw new AuthenticationNotSupportedException("Cannot authenticate, because the PLAIN mechanism is "
                    + "not supported by the server. Maybe the server expects " + "TLS first");
        }
    }

    /**
     * Parses the EHLO parameter list of the SMTP AUTH extension keyword.
     * 
     * @return the set of SASL mechanism names
     */
    private Set<String> parseMechanismsList(String authParameters) {
        String[] mechanisms = authParameters.split(" ");
        return new HashSet<String>(Arrays.asList(mechanisms));
    }

    /**
     * Creates the base64 encoded SASL PLAIN initial response.
     */
    private String constructInitialClientResponse() throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream(512);
        out.write(0);
        out.write(user.getBytes("UTF-8"));
        out.write(0);
        out.write(password.getBytes("UTF-8"));
        return Base64.getEncoder().encodeToString(out.toByteArray());
    }
}
