// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gerrit.server.mail;

import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;

import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.entities.Account;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.ReviewerSet;
import com.google.gerrit.server.account.AccountResolver;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.revwalk.FooterKey;
import org.eclipse.jgit.revwalk.FooterLine;

public class MailUtil {

  public static MailRecipients getRecipientsFromFooters(
      AccountResolver accountResolver, List<FooterLine> footerLines)
      throws IOException, ConfigInvalidException {
    MailRecipients recipients = new MailRecipients();
    for (FooterLine footerLine : footerLines) {
      try {
        if (isReviewer(footerLine)) {
          recipients.reviewers.add(toAccountId(accountResolver, footerLine.getValue().trim()));
        } else if (footerLine.matches(FooterKey.CC)) {
          recipients.cc.add(toAccountId(accountResolver, footerLine.getValue().trim()));
        }
      } catch (UnprocessableEntityException e) {
        continue;
      }
    }
    return recipients;
  }

  public static MailRecipients getRecipientsFromReviewers(ReviewerSet reviewers) {
    MailRecipients recipients = new MailRecipients();
    recipients.reviewers.addAll(reviewers.byState(REVIEWER));
    recipients.cc.addAll(reviewers.byState(CC));
    return recipients;
  }

  @SuppressWarnings("deprecation")
  private static Account.Id toAccountId(AccountResolver accountResolver, String nameOrEmail)
      throws UnprocessableEntityException, IOException, ConfigInvalidException {
    return accountResolver.resolveByNameOrEmail(nameOrEmail).asUnique().account().id();
  }

  private static boolean isReviewer(FooterLine candidateFooterLine) {
    return candidateFooterLine.matches(FooterKey.SIGNED_OFF_BY)
        || candidateFooterLine.matches(FooterKey.ACKED_BY)
        || candidateFooterLine.matches(FooterConstants.REVIEWED_BY)
        || candidateFooterLine.matches(FooterConstants.TESTED_BY);
  }

  public static class MailRecipients {
    private final Set<Account.Id> reviewers;
    private final Set<Account.Id> cc;

    public MailRecipients() {
      this.reviewers = new HashSet<>();
      this.cc = new HashSet<>();
    }

    public MailRecipients(Set<Account.Id> reviewers, Set<Account.Id> cc) {
      this.reviewers = new HashSet<>(reviewers);
      this.cc = new HashSet<>(cc);
    }

    public void add(MailRecipients recipients) {
      reviewers.addAll(recipients.reviewers);
      cc.addAll(recipients.cc);
    }

    public void remove(Account.Id toRemove) {
      reviewers.remove(toRemove);
      cc.remove(toRemove);
    }

    public Set<Account.Id> getReviewers() {
      return Collections.unmodifiableSet(reviewers);
    }

    public Set<Account.Id> getCcOnly() {
      final Set<Account.Id> cc = new HashSet<>(this.cc);
      cc.removeAll(reviewers);
      return Collections.unmodifiableSet(cc);
    }

    public Set<Account.Id> getAll() {
      final Set<Account.Id> all = new HashSet<>(reviewers.size() + cc.size());
      all.addAll(reviewers);
      all.addAll(cc);
      return Collections.unmodifiableSet(all);
    }
  }

  /** allow wildcard matching for {@code domains} */
  public static Pattern glob(String[] domains) {
    // if domains is not set, match anything
    if (domains == null || domains.length == 0) {
      return Pattern.compile(".*");
    }

    StringBuilder sb = new StringBuilder();
    for (String domain : domains) {
      String quoted = "\\Q" + domain.replace("\\E", "\\E\\\\E\\Q") + "\\E|";
      sb.append(quoted.replace("*", "\\E.*\\Q"));
    }
    return Pattern.compile(sb.substring(0, sb.length() - 1));
  }
}

@@ -793,16 +793,63 @@
 IEWER;%0A%0A
+import com.google.common.flogger.FluentLogger;%0A
 import c
@@ -1435,16 +1435,95 @@
 ilUtil %7B
+%0A  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 %0A%0A  publ
@@ -1859,33 +1859,31 @@
     
-recipients.reviewers.add(
+Account.Id accountId = 
 toAc
@@ -1928,32 +1928,209 @@
 tValue().trim())
+;%0A          recipients.reviewers.add(accountId);%0A          logger.atFine().log(%0A              %22Added account %25d from footer line %5C%22%25s%5C%22 as reviewer%22, accountId.get(), footerLine
 );%0A        %7D els
@@ -2185,26 +2185,31 @@
     
-recipients.cc.add(
+Account.Id accountId = 
 toAc
@@ -2266,69 +2266,358 @@
 m())
-);%0A        %7D%0A      %7D catch (UnprocessableEntityException e) %7B
+;%0A          recipients.cc.add(accountId);%0A          logger.atFine().log(%0A              %22Added account %25d from footer line %5C%22%25s%5C%22 as cc%22, accountId.get(), footerLine);%0A        %7D%0A      %7D catch (UnprocessableEntityException e) %7B%0A        logger.atFine().log(%0A            %22Skip adding reviewer/cc from footer line %5C%22%25s%5C%22: %25s%22, footerLine, e.getMessage());
 %0A   

Write debug log if reviewer/cc from footer is added/skipped