/*
 * Copyright (C) 2015 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.android.voicemail.impl.sync;

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION_CODES;
import android.os.UserManager;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.util.ArraySet;
import com.android.dialer.common.Assert;
import com.android.dialer.common.PerAccountSharedPreferences;
import com.android.dialer.common.concurrent.ThreadUtil;
import com.android.dialer.storage.StorageComponent;
import com.android.voicemail.VoicemailClient.ActivationStateListener;
import com.android.voicemail.impl.OmtpConstants;
import com.android.voicemail.impl.VisualVoicemailPreferences;
import com.android.voicemail.impl.VoicemailStatus;
import com.android.voicemail.impl.sms.StatusMessage;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Tracks the activation state of a visual voicemail phone account. An account is considered
 * activated if it has valid connection information from the {@link StatusMessage} stored on the
 * device. Once activation/provisioning is completed, {@link #addAccount(Context,
 * PhoneAccountHandle, StatusMessage)} should be called to store the connection information. When an
 * account is removed or if the connection information is deemed invalid, {@link
 * #removeAccount(Context, PhoneAccountHandle)} should be called to clear the connection information
 * and allow reactivation.
 */
@TargetApi(VERSION_CODES.O)
public class VvmAccountManager {
  public static final String TAG = "VvmAccountManager";

  @VisibleForTesting static final String IS_ACCOUNT_ACTIVATED = "is_account_activated";

  private static final Set<ActivationStateListener> listeners = new ArraySet<>();

  public static void addAccount(
      Context context, PhoneAccountHandle phoneAccountHandle, StatusMessage statusMessage) {
    VisualVoicemailPreferences preferences =
        new VisualVoicemailPreferences(context, phoneAccountHandle);
    statusMessage.putStatus(preferences.edit()).apply();
    setAccountActivated(context, phoneAccountHandle, true);

    ThreadUtil.postOnUiThread(
        () -> {
          for (ActivationStateListener listener : listeners) {
            listener.onActivationStateChanged(phoneAccountHandle, true);
          }
        });
  }

  public static void removeAccount(Context context, PhoneAccountHandle phoneAccount) {
    VoicemailStatus.disable(context, phoneAccount);
    setAccountActivated(context, phoneAccount, false);
    VisualVoicemailPreferences preferences = new VisualVoicemailPreferences(context, phoneAccount);
    preferences
        .edit()
        .putString(OmtpConstants.IMAP_USER_NAME, null)
        .putString(OmtpConstants.IMAP_PASSWORD, null)
        .apply();
    ThreadUtil.postOnUiThread(
        () -> {
          for (ActivationStateListener listener : listeners) {
            listener.onActivationStateChanged(phoneAccount, false);
          }
        });
  }

  public static boolean isAccountActivated(Context context, PhoneAccountHandle phoneAccount) {
    Assert.isNotNull(phoneAccount);
    PerAccountSharedPreferences preferences =
        getPreferenceForActivationState(context, phoneAccount);
    migrateActivationState(context, preferences, phoneAccount);
    return preferences.getBoolean(IS_ACCOUNT_ACTIVATED, false);
  }

  @NonNull
  public static List<PhoneAccountHandle> getActiveAccounts(Context context) {
    List<PhoneAccountHandle> results = new ArrayList<>();
    for (PhoneAccountHandle phoneAccountHandle :
        context.getSystemService(TelecomManager.class).getCallCapablePhoneAccounts()) {
      if (isAccountActivated(context, phoneAccountHandle)) {
        results.add(phoneAccountHandle);
      }
    }
    return results;
  }

  @MainThread
  public static void addListener(ActivationStateListener listener) {
    Assert.isMainThread();
    listeners.add(listener);
  }

  @MainThread
  public static void removeListener(ActivationStateListener listener) {
    Assert.isMainThread();
    listeners.remove(listener);
  }

  /**
   * The activation state is moved from credential protected storage to device protected storage
   * after v10, so it can be checked under FBE. The state should be migrated to avoid reactivation.
   */
  private static void migrateActivationState(
      Context context,
      PerAccountSharedPreferences deviceProtectedPreference,
      PhoneAccountHandle phoneAccountHandle) {
    if (!context.getSystemService(UserManager.class).isUserUnlocked()) {
      return;
    }
    if (deviceProtectedPreference.contains(IS_ACCOUNT_ACTIVATED)) {
      return;
    }

    PerAccountSharedPreferences credentialProtectedPreference =
        new VisualVoicemailPreferences(context, phoneAccountHandle);

    deviceProtectedPreference
        .edit()
        .putBoolean(
            IS_ACCOUNT_ACTIVATED,
            credentialProtectedPreference.getBoolean(IS_ACCOUNT_ACTIVATED, false))
        .apply();
  }

  private static void setAccountActivated(
      Context context, PhoneAccountHandle phoneAccountHandle, boolean activated) {
    Assert.isNotNull(phoneAccountHandle);
    getPreferenceForActivationState(context, phoneAccountHandle)
        .edit()
        .putBoolean(IS_ACCOUNT_ACTIVATED, activated)
        .apply();
  }

  private static PerAccountSharedPreferences getPreferenceForActivationState(
      Context context, PhoneAccountHandle phoneAccountHandle) {
    return new PerAccountSharedPreferences(
        context, phoneAccountHandle, StorageComponent.get(context).unencryptedSharedPrefs());
  }
}
