/*
 * Copyright (C) 2023 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.accesslog;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.health.connect.Constants;
import android.health.connect.datatypes.Record;
import android.health.connect.datatypes.RecordTypeIdentifier;
import android.health.connect.internal.datatypes.utils.RecordMapper;
import android.os.Parcel;
import android.os.Parcelable;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * A class to represent access log which is logged whenever a package requests a read on a record
 * type
 *
 * @hide
 */
@SystemApi
public final class AccessLog implements Parcelable {
    private final List<Class<? extends Record>> mRecordTypesList = new ArrayList<>();
    private final String mPackageName;
    private final Instant mAccessTime;
    @OperationType.OperationTypes private final int mOperationType;

    /**
     * Creates an access logs object that can be used to get access log request for {@code
     * packageName}
     *
     * @param packageName name of the package that requested an access
     * @param recordTypes List of Record class type the was accessed
     * @param accessTimeInMillis time when the access was requested
     * @param operationType Type of access
     * @hide
     */
    public AccessLog(
            @NonNull String packageName,
            @NonNull @RecordTypeIdentifier.RecordType List<Integer> recordTypes,
            long accessTimeInMillis,
            @OperationType.OperationTypes int operationType) {
        Objects.requireNonNull(packageName);
        Objects.requireNonNull(recordTypes);

        mPackageName = packageName;
        RecordMapper recordMapper = RecordMapper.getInstance();
        for (@RecordTypeIdentifier.RecordType int recordType : recordTypes) {
            mRecordTypesList.add(
                    recordMapper.getRecordIdToExternalRecordClassMap().get(recordType));
        }
        mAccessTime = Instant.ofEpochMilli(accessTimeInMillis);
        mOperationType = operationType;
    }

    private AccessLog(Parcel in) {
        RecordMapper recordMapper = RecordMapper.getInstance();
        for (@RecordTypeIdentifier.RecordType int recordType : in.createIntArray()) {
            mRecordTypesList.add(
                    recordMapper.getRecordIdToExternalRecordClassMap().get(recordType));
        }
        mPackageName = in.readString();
        mAccessTime = Instant.ofEpochMilli(in.readLong());
        mOperationType = in.readInt();
    }

    @NonNull
    public static final Creator<AccessLog> CREATOR =
            new Creator<>() {
                @Override
                public AccessLog createFromParcel(Parcel in) {
                    return new AccessLog(in);
                }

                @Override
                public AccessLog[] newArray(int size) {
                    return new AccessLog[size];
                }
            };

    /** Returns List of Record types that was accessed by the app */
    @NonNull
    public List<Class<? extends Record>> getRecordTypes() {
        return mRecordTypesList;
    }

    /** Returns package name of app that accessed the records */
    @NonNull
    public String getPackageName() {
        return mPackageName;
    }

    /** Returns the instant at which the app accessed the record */
    @NonNull
    public Instant getAccessTime() {
        return mAccessTime;
    }

    /** Returns the type of operation performed by the app */
    @OperationType.OperationTypes
    public int getOperationType() {
        return mOperationType;
    }

    /** Identifier for Operation type. */
    public static final class OperationType {

        /** Identifier for read operation done on user health data. */
        public static final int OPERATION_TYPE_READ = Constants.READ;

        /** Identifier for update or insert operation done on user health data. */
        public static final int OPERATION_TYPE_UPSERT = Constants.UPSERT;

        /** Identifier for delete operation done on user health data. */
        public static final int OPERATION_TYPE_DELETE = Constants.DELETE;

        /** @hide */
        @IntDef({OPERATION_TYPE_UPSERT, OPERATION_TYPE_DELETE, OPERATION_TYPE_READ})
        @Retention(RetentionPolicy.SOURCE)
        public @interface OperationTypes {}

        private OperationType() {}
    }

    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * Flatten this object in to a Parcel.
     *
     * @param dest The Parcel in which the object should be written.
     * @param flags Additional flags about how the object should be written. May be 0 or {@link
     *     #PARCELABLE_WRITE_RETURN_VALUE}.
     */
    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        int recordTypeCount = mRecordTypesList.size();
        RecordMapper recordMapper = RecordMapper.getInstance();
        @RecordTypeIdentifier.RecordType int[] recordTypes = new int[recordTypeCount];
        for (int i = 0; i < recordTypeCount; i++) {
            recordTypes[i] = recordMapper.getRecordType(mRecordTypesList.get(i));
        }
        dest.writeIntArray(recordTypes);
        dest.writeString(mPackageName);
        dest.writeLong(mAccessTime.toEpochMilli());
        dest.writeInt(mOperationType);
    }
}