/FileExtraction/Java_unseen/Kegbot_kegbot-android/kegtab/src/main/java/org/kegbot/core/AuthenticationManager.java
/*
 * Copyright 2003-2020 The Kegbot Project contributors <info@kegbot.org>
 *
 * This file is part of the Kegtab package from the Kegbot project. For
 * more information on Kegtab or Kegbot, see <http://kegbot.org/>.
 *
 * Kegtab is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 2.
 *
 * Kegtab is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with Kegtab. If not, see <http://www.gnu.org/licenses/>.
 */
package org.kegbot.core;

import android.content.Context;
import android.util.Log;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Sets;
import com.squareup.otto.Bus;

import org.kegbot.app.config.AppConfiguration;
import org.kegbot.backend.Backend;
import org.kegbot.backend.BackendException;
import org.kegbot.backend.NotFoundException;
import org.kegbot.proto.Models.User;

import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * @author
 */
public class AuthenticationManager extends Manager {

  private static final String TAG = AuthenticationManager.class.getSimpleName();

  private static final long CACHE_EXPIRE_HOURS = 3;

  private final Backend mApi;

  private final AppConfiguration mConfig;

  private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();

  private User fetchUserForToken(AuthenticationToken token) throws BackendException {
    Log.d(TAG, "Loading token");
    org.kegbot.proto.Models.AuthenticationToken tok = mApi.getAuthToken(token
        .getAuthDevice(), token.getTokenValue());
    Log.d(TAG, "Got auth token: " + tok);
    if (tok == null) {
      throw new NotFoundException("Unknown token.");
    }
    if (!tok.getEnabled()) {
      throw new NotFoundException("Token not enabled.");
    } else if (!tok.hasUser()) {
      throw new NotFoundException("Token not assigned.");
    }
    return tok.getUser();
  }

  private final LoadingCache<AuthenticationToken, User> mAuthTokenCache = CacheBuilder
      .newBuilder().expireAfterWrite(CACHE_EXPIRE_HOURS, TimeUnit.HOURS).build(
          new CacheLoader<AuthenticationToken, User>() {
            @Override
            public User load(AuthenticationToken token) throws Exception {
              final User user = fetchUserForToken(token);
              if (user != null) {
                mUserDetailCache.put(user.getUsername(), user);
              }
              return user;
            }
          }
      );

  private final LoadingCache<String, User> mUserDetailCache = CacheBuilder.newBuilder()
      .expireAfterWrite(CACHE_EXPIRE_HOURS, TimeUnit.HOURS).build(
          new CacheLoader<String, User>() {
            @Override
            public User load(String username) throws Exception {
              Log.d(TAG, "Loading user: " + username);
              return mApi.getUser(username);
            }
          }
      );

  AuthenticationManager(Bus bus, Context context, Backend api, AppConfiguration prefs) {
    super(bus);
    mApi = api;
    mConfig = prefs;
  }

  @Override
  protected void stop() {
    clearCache();
    super.stop();
  }

  public User authenticateToken(AuthenticationToken token) {
    if (!mConfig.getCacheCredentials()) {
      try {
        return fetchUserForToken(token);
      } catch (NotFoundException e) {
        Log.d(TAG, "Token is not assigned to anyone: " + token);
        return null;
      } catch (BackendException e) {
        Log.w(TAG, "Error fetching token: " + e.getCause(), e);
        return null;
      }
    }

    try {
      return mAuthTokenCache.get(token);
    } catch (ExecutionException e) {
      Throwable cause = e.getCause();
      if (cause != null && cause instanceof NotFoundException) {
        Log.d(TAG, "Token is not assigned to anyone: " + token);
        return null;
      } else {
        Log.w(TAG, "Error fetching token: " + e.getCause(), e);
      }
      return null;
    }
  }

  public User authenticateUsername(String username) {
    // TODO(mikey): use pin
    try {
      final User user = mUserDetailCache.get(username);
      if (user != null) {
        mUserDetailCache.put(user.getUsername(), user);
      }
      return user;
    } catch (ExecutionException e) {
      Log.w(TAG, "Error fetching user: " + e, e);
      return null;
    }
  }

  public Future<User> authenticateUsernameAsync(final String username) {
    return mExecutorService.submit(new Callable<User>() {
      @Override
      public User call() {
        return authenticateUsername(username);
      }
    });
  }

  public User getUserDetail(String username) {
    return mUserDetailCache.getIfPresent(username);
  }

  public Set<User> getAllRecent() {
    return Sets.newLinkedHashSet(mUserDetailCache.asMap().values());
  }

  public void clearCache() {
    // TODO(mikey): clear me on config change broadcast
    mUserDetailCache.invalidateAll();
    mAuthTokenCache.invalidateAll();
  }

}
