/*
 * Copyright (C) 2024 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 android.health.connect;
import static com.android.healthfitness.flags.Flags.FLAG_PERSONAL_HEALTH_RECORD;
import static java.util.Objects.hash;
import static java.util.Objects.requireNonNull;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.health.connect.datatypes.MedicalDataSource;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * A create request for {@link HealthConnectManager#getMedicalDataSources}.
 *
 * 
If no {@link GetMedicalDataSourcesRequest#getPackageNames() package names} are set, requests
 * all {@link MedicalDataSource}s from all packages. Otherwise the request is limited to the
 * requested package names.
 */
@FlaggedApi(FLAG_PERSONAL_HEALTH_RECORD)
public final class GetMedicalDataSourcesRequest implements Parcelable {
    @NonNull private final Set mPackageNames;
    // A full Java-language-style package name for the Android app can contain uppercase
    // or lowercase letters, numbers, and underscores ('_'). It must have at least two segments (one
    // or more dots), and individual package name parts can only start with letters. See the
    // Android developer doc.
    private static final String PACKAGE_NAME_REGEX =
            "^([A-Za-z][a-zA-Z0-9_]*\\.)+[A-Za-z][a-zA-Z0-9_]*$";
    @NonNull
    public static final Creator CREATOR =
            new Creator<>() {
                @Override
                public GetMedicalDataSourcesRequest createFromParcel(Parcel in) {
                    return new GetMedicalDataSourcesRequest(in);
                }
                @Override
                public GetMedicalDataSourcesRequest[] newArray(int size) {
                    return new GetMedicalDataSourcesRequest[size];
                }
            };
    /**
     * Creates a new instance of {@link GetMedicalDataSourcesRequest}. Please see {@link
     * GetMedicalDataSourcesRequest.Builder} for more detailed parameters information.
     */
    private GetMedicalDataSourcesRequest(@NonNull Set packageNames) {
        Objects.requireNonNull(packageNames);
        validatePackageNames(packageNames);
        mPackageNames = packageNames;
    }
    /**
     * Constructs this object with the data present in {@code parcel}. It should be in the same
     * order as {@link #writeToParcel}.
     */
    private GetMedicalDataSourcesRequest(@NonNull Parcel in) {
        Objects.requireNonNull(in);
        mPackageNames = new ArraySet<>(requireNonNull(in.createStringArrayList()));
        validatePackageNames(mPackageNames);
    }
    /**
     * Returns the package names for which {@link MedicalDataSource}s are being requested, or an
     * empty set for no filter.
     */
    @NonNull
    public Set getPackageNames() {
        return new ArraySet<>(mPackageNames);
    }
    /**
     * Validates all of the provided {@code packageNames} are valid, which matches with the {@link
     * #PACKAGE_NAME_REGEX}.
     *
     * @throws IllegalArgumentException with all invalid package names if not all {@code
     *     packageNames} are valid.
     */
    private static void validatePackageNames(Set packageNames) {
        Pattern pattern = Pattern.compile(PACKAGE_NAME_REGEX);
        Set invalidPackageNames = new HashSet<>();
        for (String packageName : packageNames) {
            Matcher matcher = pattern.matcher(packageName);
            if (!matcher.matches()) {
                invalidPackageNames.add(packageName);
            }
        }
        if (!invalidPackageNames.isEmpty()) {
            throw new IllegalArgumentException("Invalid package name(s): " + invalidPackageNames);
        }
    }
    @Override
    public int describeContents() {
        return 0;
    }
    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeStringList(new ArrayList<>(mPackageNames));
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof GetMedicalDataSourcesRequest that)) return false;
        return mPackageNames.equals(that.mPackageNames);
    }
    @Override
    public int hashCode() {
        return hash(mPackageNames);
    }
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(this.getClass().getSimpleName()).append("{");
        sb.append("packageNames=").append(mPackageNames);
        sb.append("}");
        return sb.toString();
    }
    /** Builder class for {@link GetMedicalDataSourcesRequest}. */
    public static final class Builder {
        private final Set mPackageNames = new ArraySet<>();
        /** Constructs a new {@link GetMedicalDataSourcesRequest.Builder} with no filters set. */
        public Builder() {}
        /** Constructs a clone of the other {@link GetMedicalDataSourcesRequest.Builder}. */
        public Builder(@NonNull Builder other) {
            requireNonNull(other);
            mPackageNames.addAll(other.mPackageNames);
        }
        /** Constructs a clone of the other {@link GetMedicalDataSourcesRequest} instance. */
        public Builder(@NonNull GetMedicalDataSourcesRequest other) {
            requireNonNull(other);
            mPackageNames.addAll(other.getPackageNames());
        }
        /**
         * Adds a package name to limit this request to.
         *
         * If the list of package names is empty, {@link MedicalDataSource}s for all packages
         * will be requested. Otherwise only those for the added package names are requested.
         *
         * @throws IllegalArgumentException if the provided {@code packageName} is not valid.
         */
        @NonNull
        public Builder addPackageName(@NonNull String packageName) {
            Objects.requireNonNull(packageName);
            validatePackageNames(Set.of(packageName));
            mPackageNames.add(packageName);
            return this;
        }
        /** Clears all package names. */
        @NonNull
        public Builder clearPackageNames() {
            mPackageNames.clear();
            return this;
        }
        /**
         * Returns a new instance of {@link GetMedicalDataSourcesRequest} with the specified
         * parameters.
         */
        @NonNull
        public GetMedicalDataSourcesRequest build() {
            return new GetMedicalDataSourcesRequest(mPackageNames);
        }
    }
}