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

import static com.android.metrics.NetworkNsdReported.Builder;

import android.stats.connectivity.MdnsQueryResult;
import android.stats.connectivity.NsdEventType;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.ConnectivityStatsLog;

import java.util.Random;

/**
 * Class to record the NetworkNsdReported into statsd. Each client should create this class to
 * report its data.
 */
public class NetworkNsdReportedMetrics {
    // The upper bound for the random number used in metrics data sampling determines the possible
    // sample rate.
    private static final int RANDOM_NUMBER_UPPER_BOUND = 1000;
    // The client id.
    private final int mClientId;
    private final Dependencies mDependencies;
    private final Random mRandom;

    public NetworkNsdReportedMetrics(int clientId) {
        this(clientId, new Dependencies());
    }

    @VisibleForTesting
    NetworkNsdReportedMetrics(int clientId, Dependencies dependencies) {
        mClientId = clientId;
        mDependencies = dependencies;
        mRandom = dependencies.makeRandomGenerator();
    }

    /**
     * Dependencies of NetworkNsdReportedMetrics, for injection in tests.
     */
    public static class Dependencies {

        /**
         * @see ConnectivityStatsLog
         */
        public void statsWrite(NetworkNsdReported event) {
            ConnectivityStatsLog.write(ConnectivityStatsLog.NETWORK_NSD_REPORTED,
                    event.getIsLegacy(),
                    event.getClientId(),
                    event.getTransactionId(),
                    event.getIsKnownService(),
                    event.getType().getNumber(),
                    event.getEventDurationMillisec(),
                    event.getQueryResult().getNumber(),
                    event.getFoundServiceCount(),
                    event.getFoundCallbackCount(),
                    event.getLostCallbackCount(),
                    event.getRepliedRequestsCount(),
                    event.getSentQueryCount(),
                    event.getSentPacketCount(),
                    event.getConflictDuringProbingCount(),
                    event.getConflictAfterProbingCount(),
                    event.getRandomNumber());
        }

        /**
         * @see Random
         */
        public Random makeRandomGenerator() {
            return new Random();
        }
    }

    private Builder makeReportedBuilder(boolean isLegacy, int transactionId) {
        final Builder builder = NetworkNsdReported.newBuilder();
        builder.setIsLegacy(isLegacy);
        builder.setClientId(mClientId);
        builder.setRandomNumber(mRandom.nextInt(RANDOM_NUMBER_UPPER_BOUND));
        builder.setTransactionId(transactionId);
        return builder;
    }

    /**
     * Report service registration succeeded metric data.
     *
     * @param isLegacy Whether this call is using legacy backend.
     * @param transactionId The transaction id of service registration.
     * @param durationMs The duration of service registration success.
     */
    public void reportServiceRegistrationSucceeded(boolean isLegacy, int transactionId,
            long durationMs) {
        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
        builder.setType(NsdEventType.NET_REGISTER);
        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_REGISTERED);
        builder.setEventDurationMillisec(durationMs);
        mDependencies.statsWrite(builder.build());
    }

    /**
     * Report service registration failed metric data.
     *
     * @param isLegacy Whether this call is using legacy backend.
     * @param transactionId The transaction id of service registration.
     * @param durationMs The duration of service registration failed.
     */
    public void reportServiceRegistrationFailed(boolean isLegacy, int transactionId,
            long durationMs) {
        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
        builder.setType(NsdEventType.NET_REGISTER);
        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_REGISTRATION_FAILED);
        builder.setEventDurationMillisec(durationMs);
        mDependencies.statsWrite(builder.build());
    }

    /**
     * Report service unregistration success metric data.
     *
     * @param isLegacy Whether this call is using legacy backend.
     * @param transactionId The transaction id of service registration.
     * @param durationMs The duration of service stayed registered.
     * @param repliedRequestsCount The replied request count of this service before unregistered it.
     * @param sentPacketCount The total sent packet count of this service before unregistered it.
     * @param conflictDuringProbingCount The number of conflict during probing.
     * @param conflictAfterProbingCount The number of conflict after probing.
     */
    public void reportServiceUnregistration(boolean isLegacy, int transactionId, long durationMs,
            int repliedRequestsCount, int sentPacketCount, int conflictDuringProbingCount,
            int conflictAfterProbingCount) {
        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
        builder.setType(NsdEventType.NET_REGISTER);
        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_UNREGISTERED);
        builder.setEventDurationMillisec(durationMs);
        builder.setRepliedRequestsCount(repliedRequestsCount);
        builder.setSentPacketCount(sentPacketCount);
        builder.setConflictDuringProbingCount(conflictDuringProbingCount);
        builder.setConflictAfterProbingCount(conflictAfterProbingCount);
        mDependencies.statsWrite(builder.build());
    }

    /**
     * Report service discovery started metric data.
     *
     * @param isLegacy Whether this call is using legacy backend.
     * @param transactionId The transaction id of service discovery.
     */
    public void reportServiceDiscoveryStarted(boolean isLegacy, int transactionId) {
        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
        builder.setType(NsdEventType.NET_DISCOVER);
        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_DISCOVERY_STARTED);
        mDependencies.statsWrite(builder.build());
    }

    /**
     * Report service discovery failed metric data.
     *
     * @param isLegacy Whether this call is using legacy backend.
     * @param transactionId The transaction id of service discovery.
     * @param durationMs The duration of service discovery failed.
     */
    public void reportServiceDiscoveryFailed(boolean isLegacy, int transactionId,
            long durationMs) {
        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
        builder.setType(NsdEventType.NET_DISCOVER);
        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_DISCOVERY_FAILED);
        builder.setEventDurationMillisec(durationMs);
        mDependencies.statsWrite(builder.build());
    }

    /**
     * Report service discovery stop metric data.
     *
     * @param isLegacy Whether this call is using legacy backend.
     * @param transactionId The transaction id of service discovery.
     * @param durationMs The duration of discovering services.
     * @param foundCallbackCount The count of found service callbacks before stop discovery.
     * @param lostCallbackCount The count of lost service callbacks before stop discovery.
     * @param servicesCount The count of found services.
     * @param sentQueryCount The count of sent queries before stop discovery.
     */
    public void reportServiceDiscoveryStop(boolean isLegacy, int transactionId, long durationMs,
            int foundCallbackCount, int lostCallbackCount, int servicesCount, int sentQueryCount,
            boolean isServiceFromCache) {
        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
        builder.setType(NsdEventType.NET_DISCOVER);
        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_DISCOVERY_STOP);
        builder.setEventDurationMillisec(durationMs);
        builder.setFoundCallbackCount(foundCallbackCount);
        builder.setLostCallbackCount(lostCallbackCount);
        builder.setFoundServiceCount(servicesCount);
        builder.setSentQueryCount(sentQueryCount);
        builder.setIsKnownService(isServiceFromCache);
        mDependencies.statsWrite(builder.build());
    }

    /**
     * Report service resolution success metric data.
     *
     * @param isLegacy Whether this call is using legacy backend.
     * @param transactionId The transaction id of service resolution.
     * @param durationMs The duration of resolving services.
     * @param isServiceFromCache Whether the resolved service is from cache.
     * @param sentQueryCount The count of sent queries during resolving.
     */
    public void reportServiceResolved(boolean isLegacy, int transactionId, long durationMs,
            boolean isServiceFromCache, int sentQueryCount) {
        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
        builder.setType(NsdEventType.NET_RESOLVE);
        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_RESOLVED);
        builder.setEventDurationMillisec(durationMs);
        builder.setIsKnownService(isServiceFromCache);
        builder.setSentQueryCount(sentQueryCount);
        mDependencies.statsWrite(builder.build());
    }

    /**
     * Report service resolution failed metric data.
     *
     * @param isLegacy Whether this call is using legacy backend.
     * @param transactionId The transaction id of service resolution.
     * @param durationMs The duration of service resolution failed.
     */
    public void reportServiceResolutionFailed(boolean isLegacy, int transactionId,
            long durationMs) {
        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
        builder.setType(NsdEventType.NET_RESOLVE);
        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_RESOLUTION_FAILED);
        builder.setEventDurationMillisec(durationMs);
        mDependencies.statsWrite(builder.build());
    }

    /**
     * Report service resolution stop metric data.
     *
     * @param isLegacy Whether this call is using legacy backend.
     * @param transactionId The transaction id of service resolution.
     * @param durationMs The duration before stop resolving the service.
     * @param sentQueryCount The count of sent queries during resolving.
     */
    public void reportServiceResolutionStop(boolean isLegacy, int transactionId, long durationMs,
            int sentQueryCount) {
        final Builder builder = makeReportedBuilder(isLegacy, transactionId);
        builder.setType(NsdEventType.NET_RESOLVE);
        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_RESOLUTION_STOP);
        builder.setEventDurationMillisec(durationMs);
        builder.setSentQueryCount(sentQueryCount);
        mDependencies.statsWrite(builder.build());
    }

    /**
     * Report service info callback registered metric data.
     *
     * @param transactionId The transaction id of service info callback registration.
     */
    public void reportServiceInfoCallbackRegistered(int transactionId) {
        // service info callback is always using new backend.
        final Builder builder = makeReportedBuilder(false /* isLegacy */, transactionId);
        builder.setType(NsdEventType.NET_SERVICE_INFO_CALLBACK);
        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_INFO_CALLBACK_REGISTERED);
        mDependencies.statsWrite(builder.build());
    }

    /**
     * Report service info callback registration failed metric data.
     *
     * @param transactionId The transaction id of service callback registration.
     */
    public void reportServiceInfoCallbackRegistrationFailed(int transactionId) {
        // service info callback is always using new backend.
        final Builder builder = makeReportedBuilder(false /* isLegacy */, transactionId);
        builder.setType(NsdEventType.NET_SERVICE_INFO_CALLBACK);
        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_INFO_CALLBACK_REGISTRATION_FAILED);
        mDependencies.statsWrite(builder.build());
    }

    /**
     * Report service callback unregistered metric data.
     *
     * @param transactionId The transaction id of service callback registration.
     * @param durationMs The duration of service callback stayed registered.
     * @param updateCallbackCount The count of service update callbacks during this registration.
     * @param lostCallbackCount The count of service lost callbacks during this registration.
     * @param isServiceFromCache Whether the resolved service is from cache.
     * @param sentQueryCount The count of sent queries during this registration.
     */
    public void reportServiceInfoCallbackUnregistered(int transactionId, long durationMs,
            int updateCallbackCount, int lostCallbackCount, boolean isServiceFromCache,
            int sentQueryCount) {
        // service info callback is always using new backend.
        final Builder builder = makeReportedBuilder(false /* isLegacy */, transactionId);
        builder.setType(NsdEventType.NET_SERVICE_INFO_CALLBACK);
        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_INFO_CALLBACK_UNREGISTERED);
        builder.setEventDurationMillisec(durationMs);
        builder.setFoundCallbackCount(updateCallbackCount);
        builder.setLostCallbackCount(lostCallbackCount);
        builder.setIsKnownService(isServiceFromCache);
        builder.setSentQueryCount(sentQueryCount);
        mDependencies.statsWrite(builder.build());
    }
}
