package com.hubspot.smtp.client;

import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Longs;

/**
 * The parsed response to the EHLO command.
 *
 * <p>This class is thread-safe.
 */
public class EhloResponse {
  static final EhloResponse EMPTY = EhloResponse.parse("", Collections.emptyList());

  private static final Splitter WHITESPACE_SPLITTER = Splitter.on(CharMatcher.whitespace());

  private final String ehloDomain;
  private final ImmutableSet<String> supportedExtensions;

  private EnumSet<Extension> extensions;
  private Optional<Long> maxMessageSize = Optional.empty();
  private boolean isAuthPlainSupported;
  private boolean isAuthLoginSupported;
  private boolean isAuthXoauth2Supported;

  /**
   * Parses an EHLO response.
   *
   * @param  ehloDomain the domain provided with the EHLO command (e.g. "example.com" if "EHLO example.com"
   *                    was sent to the remote server)
   * @param  lines the lines returned by the server
   * @return an {@link EhloResponse} object representing the response
   */
  public static EhloResponse parse(String ehloDomain, Iterable<CharSequence> lines) {
    return parse(ehloDomain, lines, EnumSet.noneOf(Extension.class));
  }

  /**
   * Parses an EHLO response.
   *
   * @param  ehloDomain the domain provided with the EHLO command (e.g. "example.com" if "EHLO example.com"
   *                    was sent to the remote server)
   * @param  lines the lines returned by the server
   * @param  disabledExtensions extensions which should not be marked as supported in the returned {@code EhloResponse},
   *                    even if the server says it supports them
   * @return an {@link EhloResponse} object representing the response
   */
  public static EhloResponse parse(String ehloDomain, Iterable<CharSequence> lines, EnumSet<Extension> disabledExtensions) {
    return new EhloResponse(ehloDomain, lines, disabledExtensions);
  }

  private EhloResponse(String ehloDomain, Iterable<CharSequence> lines, EnumSet<Extension> disabledExtensions) {
    this.ehloDomain = ehloDomain;
    this.extensions = EnumSet.noneOf(Extension.class);

    for (CharSequence line : lines) {
      List<String> parts = WHITESPACE_SPLITTER.splitToList(line);

      Optional<Extension> ext = Extension.find(parts.get(0));
      if (ext.isPresent()) {
        if (disabledExtensions.contains(ext.get())) {
          continue;
        }

        extensions.add(ext.get());
      }

      switch (parts.get(0).toLowerCase()) {
        case "auth":
          parseAuth(parts);
          break;
        case "size":
          parseSize(parts);
          break;
        default:
          break;
      }
    }

    supportedExtensions = ImmutableSet.copyOf(Iterables.transform(lines, CharSequence::toString));
  }

  private void parseSize(List<String> parts) {
    if (parts.size() > 1) {
      Optional<Long> maybeSize = Optional.ofNullable(Longs.tryParse(parts.get(1)));

      if (maybeSize.isPresent() && maybeSize.get() > 0) {
        maxMessageSize = maybeSize;
      }
    }
  }

  private void parseAuth(List<String> parts) {
    for (String s : parts) {
      if (s.equalsIgnoreCase("plain")) {
        isAuthPlainSupported = true;
      } else if (s.equalsIgnoreCase("login")) {
        isAuthLoginSupported = true;
      } else if (s.equalsIgnoreCase("xoauth2")) {
        isAuthXoauth2Supported = true;
      }
    }
  }

  /**
   * Gets the domain provided with the EHLO command (e.g. "example.com" if "EHLO example.com"
   * was sent to the remote server).
   */
  public String getEhloDomain() {
    return ehloDomain;
  }

  /**
   * Gets whether the specified {@link Extension} is supported by the server.
   */
  public boolean isSupported(Extension ext) {
    return extensions.contains(ext);
  }

  /**
   * Gets whether PLAIN authentication is supported.
   */
  public boolean isAuthPlainSupported() {
    return isAuthPlainSupported;
  }

  /**
   * Gets whether LOGIN authentication is supported.
   */
  public boolean isAuthLoginSupported() {
    return isAuthLoginSupported;
  }

  /**
   * Gets whether XOAUTH2 authentication is supported.
   */
  public boolean isAuthXoauth2Supported() {
    return isAuthXoauth2Supported;
  }

  /**
   * Gets the maximum message size if specified by the server.
   */
  public Optional<Long> getMaxMessageSize() {
    return maxMessageSize;
  }

  /**
   * Gets the raw extensions returned by the server.
   */
  public Set<String> getSupportedExtensions() {
    return supportedExtensions;
  }
}