/*
 * 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 com.android.server.telecom.metrics;

import static com.android.server.telecom.TelecomStatsLog.TELECOM_API_STATS;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.StatsManager;
import android.content.Context;
import android.os.Looper;
import android.telecom.Log;
import android.util.StatsEvent;

import androidx.annotation.VisibleForTesting;

import com.android.server.telecom.TelecomStatsLog;
import com.android.server.telecom.nano.PulledAtomsClass;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class ApiStats extends TelecomPulledAtom {
    public static final int API_UNSPECIFIC = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_UNSPECIFIED;
    public static final int API_ACCEPTHANDOVER = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_ACCEPT_HANDOVER;
    public static final int API_ACCEPTRINGINGCALL = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_ACCEPT_RINGING_CALL;
    public static final int API_ACCEPTRINGINGCALLWITHVIDEOSTATE = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_ACCEPT_RINGING_CALL_WITH_VIDEO_STATE;
    public static final int API_ADDCALL = TelecomStatsLog.TELECOM_API_STATS__API_NAME__API_ADD_CALL;
    public static final int API_ADDNEWINCOMINGCALL = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_ADD_NEW_INCOMING_CALL;
    public static final int API_ADDNEWINCOMINGCONFERENCE = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_ADD_NEW_INCOMING_CONFERENCE;
    public static final int API_ADDNEWUNKNOWNCALL = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_ADD_NEW_UNKNOWN_CALL;
    public static final int API_CANCELMISSEDCALLSNOTIFICATION = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_CANCEL_MISSED_CALLS_NOTIFICATION;
    public static final int API_CLEARACCOUNTS = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_CLEAR_ACCOUNTS;
    public static final int API_CREATELAUNCHEMERGENCYDIALERINTENT = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_CREATE_LAUNCH_EMERGENCY_DIALER_INTENT;
    public static final int API_CREATEMANAGEBLOCKEDNUMBERSINTENT = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_CREATE_MANAGE_BLOCKED_NUMBERS_INTENT;
    public static final int API_DUMP = TelecomStatsLog.TELECOM_API_STATS__API_NAME__API_DUMP;
    public static final int API_DUMPCALLANALYTICS = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_DUMP_CALL_ANALYTICS;
    public static final int API_ENABLEPHONEACCOUNT = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_ENABLE_PHONE_ACCOUNT;
    public static final int API_ENDCALL = TelecomStatsLog.TELECOM_API_STATS__API_NAME__API_END_CALL;
    public static final int API_GETADNURIFORPHONEACCOUNT = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_ADN_URI_FOR_PHONE_ACCOUNT;
    public static final int API_GETALLPHONEACCOUNTHANDLES = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_ALL_PHONE_ACCOUNT_HANDLES;
    public static final int API_GETALLPHONEACCOUNTS = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_ALL_PHONE_ACCOUNTS;
    public static final int API_GETALLPHONEACCOUNTSCOUNT = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_ALL_PHONE_ACCOUNTS_COUNT;
    public static final int API_GETCALLCAPABLEPHONEACCOUNTS = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_CALL_CAPABLE_PHONE_ACCOUNTS;
    public static final int API_GETCALLSTATE = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_CALL_STATE;
    public static final int API_GETCALLSTATEUSINGPACKAGE = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_CALL_STATE_USING_PACKAGE;
    public static final int API_GETCURRENTTTYMODE = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_CURRENT_TTY_MODE;
    public static final int API_GETDEFAULTDIALERPACKAGE = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_DEFAULT_DIALER_PACKAGE;
    public static final int API_GETDEFAULTDIALERPACKAGEFORUSER = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_DEFAULT_DIALER_PACKAGE_FOR_USER;
    public static final int API_GETDEFAULTOUTGOINGPHONEACCOUNT = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_DEFAULT_OUTGOING_PHONE_ACCOUNT;
    public static final int API_GETDEFAULTPHONEAPP = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_DEFAULT_PHONE_APP;
    public static final int API_GETLINE1NUMBER = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_LINE1_NUMBER;
    public static final int API_GETOWNSELFMANAGEDPHONEACCOUNTS = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_OWN_SELF_MANAGED_PHONE_ACCOUNTS;
    public static final int API_GETPHONEACCOUNT = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_PHONE_ACCOUNT;
    public static final int API_GETPHONEACCOUNTSFORPACKAGE = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_PHONE_ACCOUNTS_FOR_PACKAGE;
    public static final int API_GETPHONEACCOUNTSSUPPORTINGSCHEME = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_PHONE_ACCOUNTS_SUPPORTING_SCHEME;
    public static final int API_GETREGISTEREDPHONEACCOUNTS = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_REGISTERED_PHONE_ACCOUNTS;
    public static final int API_GETSELFMANAGEDPHONEACCOUNTS = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_SELF_MANAGED_PHONE_ACCOUNTS;
    public static final int API_GETSIMCALLMANAGER = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_SIM_CALL_MANAGER;
    public static final int API_GETSIMCALLMANAGERFORUSER = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_SIM_CALL_MANAGER_FOR_USER;
    public static final int API_GETSYSTEMDIALERPACKAGE = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_SYSTEM_DIALER_PACKAGE;
    public static final int API_GETUSERSELECTEDOUTGOINGPHONEACCOUNT = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT;
    public static final int API_GETVOICEMAILNUMBER = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_GET_VOICE_MAIL_NUMBER;
    public static final int API_HANDLEPINMMI = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_HANDLE_PIN_MMI;
    public static final int API_HANDLEPINMMIFORPHONEACCOUNT = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_HANDLE_PIN_MMI_FOR_PHONE_ACCOUNT;
    public static final int API_HASMANAGEONGOINGCALLSPERMISSION = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_HAS_MANAGE_ONGOING_CALLS_PERMISSION;
    public static final int API_ISINCALL = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_IS_IN_CALL;
    public static final int API_ISINCOMINGCALLPERMITTED = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_IS_IN_EMERGENCY_CALL;
    public static final int API_ISINEMERGENCYCALL = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_IS_IN_MANAGED_CALL;
    public static final int API_ISINMANAGEDCALL = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_IS_IN_SELF_MANAGED_CALL;
    public static final int API_ISINSELFMANAGEDCALL = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_IS_INCOMING_CALL_PERMITTED;
    public static final int API_ISOUTGOINGCALLPERMITTED = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_IS_OUTGOING_CALL_PERMITTED;
    public static final int API_ISRINGING = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_IS_RINGING;
    public static final int API_ISTTYSUPPORTED = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_IS_TTY_SUPPORTED;
    public static final int API_ISVOICEMAILNUMBER = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_IS_VOICE_MAIL_NUMBER;
    public static final int API_PLACECALL = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_PLACE_CALL;
    public static final int API_REGISTERPHONEACCOUNT = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_REGISTER_PHONE_ACCOUNT;
    public static final int API_SETDEFAULTDIALER = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_SET_DEFAULT_DIALER;
    public static final int API_SETUSERSELECTEDOUTGOINGPHONEACCOUNT = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT;
    public static final int API_SHOWINCALLSCREEN = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_SHOW_IN_CALL_SCREEN;
    public static final int API_SILENCERINGER = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_SILENCE_RINGER;
    public static final int API_STARTCONFERENCE = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_START_CONFERENCE;
    public static final int API_UNREGISTERPHONEACCOUNT = TelecomStatsLog
            .TELECOM_API_STATS__API_NAME__API_UNREGISTER_PHONE_ACCOUNT;
    public static final int RESULT_UNKNOWN = TelecomStatsLog
            .TELECOM_API_STATS__API_RESULT__RESULT_UNKNOWN;
    public static final int RESULT_NORMAL = TelecomStatsLog
            .TELECOM_API_STATS__API_RESULT__RESULT_SUCCESS;
    public static final int RESULT_PERMISSION = TelecomStatsLog
            .TELECOM_API_STATS__API_RESULT__RESULT_PERMISSION;
    public static final int RESULT_EXCEPTION = TelecomStatsLog
            .TELECOM_API_STATS__API_RESULT__RESULT_EXCEPTION;
    private static final String TAG = ApiStats.class.getSimpleName();
    private static final String FILE_NAME = "api_stats";
    private Map<ApiEvent, Integer> mApiStatsMap;

    public ApiStats(@NonNull Context context, @NonNull Looper looper, boolean isTestMode) {
        super(context, looper, isTestMode);
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    @Override
    public int getTag() {
        return TELECOM_API_STATS;
    }

    @Override
    protected String getFileName() {
        return FILE_NAME;
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    @Override
    public synchronized int onPull(final List<StatsEvent> data) {
        if (mPulledAtoms.telecomApiStats.length != 0) {
            Arrays.stream(mPulledAtoms.telecomApiStats).forEach(v -> data.add(
                    TelecomStatsLog.buildStatsEvent(getTag(),
                            v.getApiName(), v.getUid(), v.getApiResult(), v.getCount())));
            mApiStatsMap.clear();
            onAggregate();
            return StatsManager.PULL_SUCCESS;
        } else {
            return StatsManager.PULL_SKIP;
        }
    }

    @Override
    protected synchronized void onLoad() {
        if (mPulledAtoms.telecomApiStats != null) {
            mApiStatsMap = new HashMap<>();
            for (PulledAtomsClass.TelecomApiStats v : mPulledAtoms.telecomApiStats) {
                mApiStatsMap.put(new ApiEvent(v.getApiName(), v.getUid(), v.getApiResult()),
                        v.getCount());
            }
            mLastPulledTimestamps = mPulledAtoms.getTelecomApiStatsPullTimestampMillis();
        }
    }

    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    @Override
    public synchronized void onAggregate() {
        Log.d(TAG, "onAggregate: %s", mApiStatsMap);
        clearAtoms();
        if (mApiStatsMap.isEmpty()) {
            return;
        }
        mPulledAtoms.setTelecomApiStatsPullTimestampMillis(mLastPulledTimestamps);
        mPulledAtoms.telecomApiStats =
                new PulledAtomsClass.TelecomApiStats[mApiStatsMap.size()];
        int[] index = new int[1];
        mApiStatsMap.forEach((k, v) -> {
            mPulledAtoms.telecomApiStats[index[0]] = new PulledAtomsClass.TelecomApiStats();
            mPulledAtoms.telecomApiStats[index[0]].setApiName(k.mId);
            mPulledAtoms.telecomApiStats[index[0]].setUid(k.mCallerUid);
            mPulledAtoms.telecomApiStats[index[0]].setApiResult(k.mResult);
            mPulledAtoms.telecomApiStats[index[0]].setCount(v);
            index[0]++;
        });
        save(DELAY_FOR_PERSISTENT_MILLIS);
    }

    public void log(@NonNull ApiEvent event) {
        post(() -> {
            mApiStatsMap.put(event, mApiStatsMap.getOrDefault(event, 0) + 1);
            onAggregate();
        });
    }

    @IntDef(prefix = "API", value = {
            API_UNSPECIFIC,
            API_ACCEPTHANDOVER,
            API_ACCEPTRINGINGCALL,
            API_ACCEPTRINGINGCALLWITHVIDEOSTATE,
            API_ADDCALL,
            API_ADDNEWINCOMINGCALL,
            API_ADDNEWINCOMINGCONFERENCE,
            API_ADDNEWUNKNOWNCALL,
            API_CANCELMISSEDCALLSNOTIFICATION,
            API_CLEARACCOUNTS,
            API_CREATELAUNCHEMERGENCYDIALERINTENT,
            API_CREATEMANAGEBLOCKEDNUMBERSINTENT,
            API_DUMP,
            API_DUMPCALLANALYTICS,
            API_ENABLEPHONEACCOUNT,
            API_ENDCALL,
            API_GETADNURIFORPHONEACCOUNT,
            API_GETALLPHONEACCOUNTHANDLES,
            API_GETALLPHONEACCOUNTS,
            API_GETALLPHONEACCOUNTSCOUNT,
            API_GETCALLCAPABLEPHONEACCOUNTS,
            API_GETCALLSTATE,
            API_GETCALLSTATEUSINGPACKAGE,
            API_GETCURRENTTTYMODE,
            API_GETDEFAULTDIALERPACKAGE,
            API_GETDEFAULTDIALERPACKAGEFORUSER,
            API_GETDEFAULTOUTGOINGPHONEACCOUNT,
            API_GETDEFAULTPHONEAPP,
            API_GETLINE1NUMBER,
            API_GETOWNSELFMANAGEDPHONEACCOUNTS,
            API_GETPHONEACCOUNT,
            API_GETPHONEACCOUNTSFORPACKAGE,
            API_GETPHONEACCOUNTSSUPPORTINGSCHEME,
            API_GETREGISTEREDPHONEACCOUNTS,
            API_GETSELFMANAGEDPHONEACCOUNTS,
            API_GETSIMCALLMANAGER,
            API_GETSIMCALLMANAGERFORUSER,
            API_GETSYSTEMDIALERPACKAGE,
            API_GETUSERSELECTEDOUTGOINGPHONEACCOUNT,
            API_GETVOICEMAILNUMBER,
            API_HANDLEPINMMI,
            API_HANDLEPINMMIFORPHONEACCOUNT,
            API_HASMANAGEONGOINGCALLSPERMISSION,
            API_ISINCALL,
            API_ISINCOMINGCALLPERMITTED,
            API_ISINEMERGENCYCALL,
            API_ISINMANAGEDCALL,
            API_ISINSELFMANAGEDCALL,
            API_ISOUTGOINGCALLPERMITTED,
            API_ISRINGING,
            API_ISTTYSUPPORTED,
            API_ISVOICEMAILNUMBER,
            API_PLACECALL,
            API_REGISTERPHONEACCOUNT,
            API_SETDEFAULTDIALER,
            API_SETUSERSELECTEDOUTGOINGPHONEACCOUNT,
            API_SHOWINCALLSCREEN,
            API_SILENCERINGER,
            API_STARTCONFERENCE,
            API_UNREGISTERPHONEACCOUNT,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ApiId {
    }

    @IntDef(prefix = "RESULT", value = {
            RESULT_UNKNOWN,
            RESULT_NORMAL,
            RESULT_PERMISSION,
            RESULT_EXCEPTION,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ResultId {
    }

    public static class ApiEvent {

        @ApiId
        int mId;
        int mCallerUid;
        @ResultId
        int mResult;

        public ApiEvent(@ApiId int id, int callerUid, @ResultId int result) {
            mId = id;
            mCallerUid = callerUid;
            mResult = result;
        }

        public void setCallerUid(int uid) {
            this.mCallerUid = uid;
        }

        public void setResult(@ResultId int result) {
            this.mResult = result;
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) {
                return true;
            }
            if (!(other instanceof ApiEvent obj)) {
                return false;
            }
            return this.mId == obj.mId && this.mCallerUid == obj.mCallerUid
                    && this.mResult == obj.mResult;
        }

        @Override
        public int hashCode() {
            return Objects.hash(mId, mCallerUid, mResult);
        }

        @Override
        public String toString() {
            return "[ApiEvent: mApiId=" + mId + ", mCallerUid=" + mCallerUid
                    + ", mResult=" + mResult + "]";
        }
    }
}
