// 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.account;

import com.google.common.base.Strings;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.AvatarInfo;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.avatar.AvatarProvider;
import com.google.gwtorm.server.OrmException;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

@Singleton
public class InternalAccountDirectory extends AccountDirectory {
  static final Set<FillOptions> ID_ONLY =
      Collections.unmodifiableSet(EnumSet.of(FillOptions.ID));

  public static class Module extends AbstractModule {
    @Override
    protected void configure() {
      bind(AccountDirectory.class).to(InternalAccountDirectory.class);
    }
  }

  private final Provider<ReviewDb> db;
  private final AccountCache accountCache;
  private final DynamicItem<AvatarProvider> avatar;
  private final IdentifiedUser.GenericFactory userFactory;

  @Inject
  InternalAccountDirectory(Provider<ReviewDb> db,
      AccountCache accountCache,
      DynamicItem<AvatarProvider> avatar,
      IdentifiedUser.GenericFactory userFactory) {
    this.db = db;
    this.accountCache = accountCache;
    this.avatar = avatar;
    this.userFactory = userFactory;
  }

  @Override
  public void fillAccountInfo(
      Iterable<? extends AccountInfo> in,
      Set<FillOptions> options)
      throws DirectoryException {
    if (options.equals(ID_ONLY)) {
      return;
    }
    Multimap<Account.Id, AccountInfo> missing = ArrayListMultimap.create();
    for (AccountInfo info : in) {
      Account.Id id = new Account.Id(info._accountId);
      AccountState state = accountCache.getIfPresent(id);
      if (state != null) {
        fill(info, state.getAccount(), state.getExternalIds(), options);
      } else {
        missing.put(id, info);
      }
    }
    if (!missing.isEmpty()) {
      try {
        for (Account account : db.get().accounts().get(missing.keySet())) {
          Collection<AccountExternalId> externalIds = null;
          if (options.contains(FillOptions.USERNAME)
              || options.contains(FillOptions.SECONDARY_EMAILS)) {
            externalIds = db.get().accountExternalIds()
                .byAccount(account.getId()).toList();
          }
          for (AccountInfo info : missing.get(account.getId())) {
            fill(info, account, externalIds, options);
          }
        }
      } catch (OrmException e) {
        throw new DirectoryException(e);
      }
    }
  }

  private void fill(AccountInfo info,
      Account account,
      @Nullable Collection<AccountExternalId> externalIds,
      Set<FillOptions> options) {
    if (options.contains(FillOptions.ID)) {
      info._accountId = account.getId().get();
    } else {
      // Was previously set to look up account for filling.
      info._accountId = null;
    }
    if (options.contains(FillOptions.NAME)) {
      info.name = Strings.emptyToNull(account.getFullName());
      if (info.name == null) {
        info.name = account.getUserName();
      }
    }
    if (options.contains(FillOptions.EMAIL)) {
      info.email = account.getPreferredEmail();
    }
    if (options.contains(FillOptions.SECONDARY_EMAILS)) {
      info.secondaryEmails = externalIds != null
          ? getSecondaryEmails(account, externalIds)
          : null;
    }
    if (options.contains(FillOptions.USERNAME)) {
      info.username = externalIds != null
          ? AccountState.getUserName(externalIds)
          : null;
    }
    if (options.contains(FillOptions.AVATARS)) {
      AvatarProvider ap = avatar.get();
      if (ap != null) {
        info.avatars = new ArrayList<>(3);
        IdentifiedUser user = userFactory.create(account.getId());

        // GWT UI uses DEFAULT_SIZE (26px).
        addAvatar(ap, info, user, AvatarInfo.DEFAULT_SIZE);

        // PolyGerrit UI prefers 32px and 100px.
        if (!info.avatars.isEmpty()) {
          if (32 != AvatarInfo.DEFAULT_SIZE) {
            addAvatar(ap, info, user, 32);
          }
          if (100 != AvatarInfo.DEFAULT_SIZE) {
            addAvatar(ap, info, user, 100);
          }
        }
      }
    }
  }

  public List<String> getSecondaryEmails(Account account,
      Collection<AccountExternalId> externalIds) {
    List<String> emails = new ArrayList<>(AccountState.getEmails(externalIds));
    if (account.getPreferredEmail() != null) {
      emails.remove(account.getPreferredEmail());
    }
    Collections.sort(emails);
    return emails;
  }

  private static void addAvatar(
      AvatarProvider provider,
      AccountInfo account,
      IdentifiedUser user,
      int size) {
    String url = provider.getUrl(user, size);
    if (url != null) {
      AvatarInfo avatar = new AvatarInfo();
      avatar.url = url;
      avatar.height = size;
      account.avatars.add(avatar);
    }
  }
}

@@ -688,103 +688,8 @@
 gs;%0A
-import com.google.common.collect.ArrayListMultimap;%0Aimport com.google.common.collect.Multimap;%0A
 impo
@@ -722,24 +722,24 @@
 n.Nullable;%0A
+
 import com.g
@@ -1013,59 +1013,8 @@
 Id;%0A
-import com.google.gerrit.reviewdb.server.ReviewDb;%0A
 impo
@@ -1116,54 +1116,8 @@
 er;%0A
-import com.google.gwtorm.server.OrmException;%0A
 impo
@@ -1190,43 +1190,8 @@
 ct;%0A
-import com.google.inject.Provider;%0A
 impo
@@ -1751,47 +1751,8 @@
  %7D%0A%0A
-  private final Provider%3CReviewDb%3E db;%0A
   pr
@@ -1943,37 +1943,8 @@
 ory(
-Provider%3CReviewDb%3E db,%0A      
 Acco
@@ -2063,26 +2063,8 @@
 ) %7B%0A
-    this.db = db;%0A
     
@@ -2374,84 +2374,8 @@
   %7D%0A
-    Multimap%3CAccount.Id, AccountInfo%3E missing = ArrayListMultimap.create();%0A
     
@@ -2506,52 +2506,14 @@
 .get
-IfPresent(id);%0A      if (state != null) %7B%0A  
+(id);%0A
     
@@ -2583,713 +2583,8 @@
 s);%0A
-      %7D else %7B%0A        missing.put(id, info);%0A      %7D%0A    %7D%0A    if (!missing.isEmpty()) %7B%0A      try %7B%0A        for (Account account : db.get().accounts().get(missing.keySet())) %7B%0A          Collection%3CAccountExternalId%3E externalIds = null;%0A          if (options.contains(FillOptions.USERNAME)%0A              %7C%7C options.contains(FillOptions.SECONDARY_EMAILS)) %7B%0A            externalIds = db.get().accountExternalIds()%0A                .byAccount(account.getId()).toList();%0A          %7D%0A          for (AccountInfo info : missing.get(account.getId())) %7B%0A            fill(info, account, externalIds, options);%0A          %7D%0A        %7D%0A      %7D catch (OrmException e) %7B%0A        throw new DirectoryException(e);%0A      %7D%0A
     

Load all accounts via account cache