/*
 * Copyright 2022 Google LLC
 *
 * 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.android.libraries.mobiledatadownload.file.backends;

import android.accounts.Account;
import com.google.android.libraries.mobiledatadownload.file.common.internal.Preconditions;

/** Helper for Uri classes to serialize Android accounts. */
public final class AccountSerialization {

  private static final String SHARED_ACCOUNT_STR = "shared";

  /** A common {@link Account} with no associated user; it appears as "shared" on the filesystem. */
  public static final Account SHARED_ACCOUNT = new Account(SHARED_ACCOUNT_STR, "mobstore");

  /**
   * Validates and serializes an {@link Account} into a string representation "<type>:<name>", with
   * the exception of {@link #SHARED_ACCOUNT} which is serialized as {@link #SHARED_ACCOUNT_STR}.
   *
   * <p>The account will be URL encoded in its URI representation (so, eg, "<internal>@gmail.com"
   * will appear as "you%40gmail.com"), but not in the file path representation used to access disk.
   * {@link #SHARED_ACCOUNT} shows up as "shared" on the filesystem.
   *
   * <p>This method performs some account validation. Android Account itself requires that both the
   * type and name fields be present. In addition to this requirement, this method requires that the
   * type contain no colons (as these are the delimiter used internally for the account
   * serialization), and that neither the type nor the name include any slashes (as these are file
   * separators).
   *
   * <p>Note the Linux filesystem accepts filenames composed of any bytes except "/" and NULL.
   *
   * @throws IllegalArgumentException if the account does not conform to the specifications above
   */
  public static String serialize(Account account) {
    validate(account);
    if (isSharedAccount(account)) {
      return SHARED_ACCOUNT_STR;
    }
    return account.type + ":" + account.name;
  }

  /**
   * Parses an account string generated by {@link #serialize} back into an {@link Account}, or
   * throws {@link IllegalArgumentException} if {@code accountStr} has an unrecognized format.
   */
  public static Account deserialize(String accountStr) {
    if (isSharedAccount(accountStr)) {
      return SHARED_ACCOUNT;
    }
    int colonIdx = accountStr.indexOf(':');
    Preconditions.checkArgument(colonIdx > -1, "Malformed account");
    String type = accountStr.substring(0, colonIdx);
    String name = accountStr.substring(colonIdx + 1);
    return new Account(name, type);
  }

  static boolean isSharedAccount(String accountStr) {
    return SHARED_ACCOUNT_STR.equals(accountStr);
  }

  static boolean isSharedAccount(Account account) {
    return SHARED_ACCOUNT.equals(account);
  }

  private static void validate(Account account) {
    // Android Account already validates that name and type are not empty.
    Preconditions.checkArgument(account.type.indexOf(':') == -1, "Account type contains ':'.");
    Preconditions.checkArgument(account.type.indexOf('/') == -1, "Account type contains '/'.");
    Preconditions.checkArgument(account.name.indexOf('/') == -1, "Account name contains '/'.");
  }

  private AccountSerialization() {}
}
