// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.net.impl;

import static android.os.Process.THREAD_PRIORITY_BACKGROUND;

import java.time.Duration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** Base class for implementing a CronetLogger. */
public abstract class CronetLogger {
    public static enum CronetSource {
        // Safe default, don't use explicitly.
        CRONET_SOURCE_UNSPECIFIED,
        // The library is bundled with the application.
        CRONET_SOURCE_STATICALLY_LINKED,
        // The library is loaded from GooglePlayServices.
        CRONET_SOURCE_PLAY_SERVICES,
        // The application is using the fallback implementation.
        CRONET_SOURCE_FALLBACK,
        // The library is loaded through the bootclasspath.
        CRONET_SOURCE_PLATFORM,
    }

    /**
     * Logs a cronetEngine creation action with the details of the creation.
     *
     * @param cronetEngineId the id of the engine being created.
     * @param engineBuilderInfo the configuration of the CronetEngine being created. See {@link
     *        CronetEngineBuilderInfo}
     * @param version the version of cronet used for the engine. See {@link CronetVersion}
     * @param source the source of the cronet provider for the engine. See {@link CronetSource}
     */
    public abstract void logCronetEngineCreation(
            int cronetEngineId,
            CronetEngineBuilderInfo engineBuilderInfo,
            CronetVersion version,
            CronetSource source);

    /**
     * Logs a request/response action.
     * @param cronetEngineId the id of the engine used for the request
     * @param trafficInfo the associated traffic information. See {@link CronetTrafficInfo}
     */
    public abstract void logCronetTrafficInfo(int cronetEngineId, CronetTrafficInfo trafficInfo);

    /** Aggregates the information about a CronetEngine configuration. */
    public static class CronetEngineBuilderInfo {
        private final boolean mPublicKeyPinningBypassForLocalTrustAnchorsEnabled;
        private final String mUserAgent;
        private final String mStoragePath;
        private final boolean mQuicEnabled;
        private final boolean mHttp2Enabled;
        private final boolean mBrotiEnabled;
        private final int mHttpCacheMode;
        private final String mExperimentalOptions;
        private final boolean mNetworkQualityEstimatorEnabled;
        private final int mThreadPriority;

        public CronetEngineBuilderInfo(CronetEngineBuilderImpl builder) {
            mPublicKeyPinningBypassForLocalTrustAnchorsEnabled =
                    builder.publicKeyPinningBypassForLocalTrustAnchorsEnabled();
            mUserAgent = builder.getUserAgent();
            mStoragePath = builder.storagePath();
            mQuicEnabled = builder.quicEnabled();
            mHttp2Enabled = builder.http2Enabled();
            mBrotiEnabled = builder.brotliEnabled();
            mHttpCacheMode = builder.publicBuilderHttpCacheMode();
            mExperimentalOptions = builder.experimentalOptions();
            mNetworkQualityEstimatorEnabled = builder.networkQualityEstimatorEnabled();
            mThreadPriority = builder.threadPriority(THREAD_PRIORITY_BACKGROUND);
        }

        /** @return Whether public key pinning bypass for local trust anchors is enabled */
        public boolean isPublicKeyPinningBypassForLocalTrustAnchorsEnabled() {
            return mPublicKeyPinningBypassForLocalTrustAnchorsEnabled;
        }

        /** @return User-Agent used for URLRequests created through this CronetEngine */
        public String getUserAgent() {
            return mUserAgent;
        }

        /** @return Path to the directory used for HTTP cache and Cookie storage */
        public String getStoragePath() {
            return mStoragePath;
        }

        /** @return Whether QUIC protocol is enabled */
        public boolean isQuicEnabled() {
            return mQuicEnabled;
        }

        /** @return Whether HTTP2 protocol is enabled */
        public boolean isHttp2Enabled() {
            return mHttp2Enabled;
        }

        /** @return Whether Brotli compression is enabled */
        public boolean isBrotliEnabled() {
            return mBrotiEnabled;
        }

        /**
         * @return Whether caching of HTTP data and other information like QUIC server information
         *         is enabled
         */
        public int getHttpCacheMode() {
            return mHttpCacheMode;
        }

        /** @return Experimental options configuration used by the CronetEngine */
        public String getExperimentalOptions() {
            return mExperimentalOptions;
        }

        /** @return Whether network quality estimator is enabled */
        public boolean isNetworkQualityEstimatorEnabled() {
            return mNetworkQualityEstimatorEnabled;
        }

        /** @return The thread priority of Cronet's internal thread */
        public int getThreadPriority() {
            return mThreadPriority;
        }
    }

    /**
     * Aggregates the information about request and response traffic for a
     * particular CronetEngine.
     */
    public static class CronetTrafficInfo {
        private final long mRequestHeaderSizeInBytes;
        private final long mRequestBodySizeInBytes;
        private final long mResponseHeaderSizeInBytes;
        private final long mResponseBodySizeInBytes;
        private final int mResponseStatusCode;
        private final Duration mHeadersLatency;
        private final Duration mTotalLatency;
        private final String mNegotiatedProtocol;
        private final boolean mWasConnectionMigrationAttempted;
        private final boolean mDidConnectionMigrationSucceed;

        public CronetTrafficInfo(
                long requestHeaderSizeInBytes,
                long requestBodySizeInBytes,
                long responseHeaderSizeInBytes,
                long responseBodySizeInBytes,
                int responseStatusCode,
                Duration headersLatency,
                Duration totalLatency,
                String negotiatedProtocol,
                boolean wasConnectionMigrationAttempted,
                boolean didConnectionMigrationSucceed) {
            mRequestHeaderSizeInBytes = requestHeaderSizeInBytes;
            mRequestBodySizeInBytes = requestBodySizeInBytes;
            mResponseHeaderSizeInBytes = responseHeaderSizeInBytes;
            mResponseBodySizeInBytes = responseBodySizeInBytes;
            mResponseStatusCode = responseStatusCode;
            mHeadersLatency = headersLatency;
            mTotalLatency = totalLatency;
            mNegotiatedProtocol = negotiatedProtocol;
            mWasConnectionMigrationAttempted = wasConnectionMigrationAttempted;
            mDidConnectionMigrationSucceed = didConnectionMigrationSucceed;
        }

        /** @return The total size of headers sent in bytes */
        public long getRequestHeaderSizeInBytes() {
            return mRequestHeaderSizeInBytes;
        }

        /** @return The total size of request body sent, if any, in bytes */
        public long getRequestBodySizeInBytes() {
            return mRequestBodySizeInBytes;
        }

        /** @return The total size of headers received in bytes */
        public long getResponseHeaderSizeInBytes() {
            return mResponseHeaderSizeInBytes;
        }

        /** @return The total size of response body, if any, received in bytes */
        public long getResponseBodySizeInBytes() {
            return mResponseBodySizeInBytes;
        }

        /** @return The response status code of the request */
        public int getResponseStatusCode() {
            return mResponseStatusCode;
        }

        /**
         * The time it took from starting the request to receiving the full set of
         * response headers.
         *
         * @return The time to get response headers
         */
        public Duration getHeadersLatency() {
            return mHeadersLatency;
        }

        /**
         * The time it took from starting the request to receiving the entire
         * response.
         *
         * @return The time to get total response
         */
        public Duration getTotalLatency() {
            return mTotalLatency;
        }

        /** @return The negotiated protocol used for the traffic */
        public String getNegotiatedProtocol() {
            return mNegotiatedProtocol;
        }

        /** @return True if the connection migration was attempted, else False */
        public boolean wasConnectionMigrationAttempted() {
            return mWasConnectionMigrationAttempted;
        }

        /** @return True if the connection migration was attempted and succeeded, else False */
        public boolean didConnectionMigrationSucceed() {
            return mDidConnectionMigrationSucceed;
        }
    }

    /** Holds information about the cronet version used for a cronetEngine. */
    public static class CronetVersion {
        private static final Pattern VERSION_PATTERN =
                Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)");

        private final int mMajorVersion;
        private final int mMinorVersion;
        private final int mBuildVersion;
        private final int mPatchVersion;

        /**
         * Pass the cronet version string here and
         * it would be split. The string comes in the format
         * MAJOR.MINOR.BUILD.PATCH
         */
        public CronetVersion(String version) {
            Matcher m = VERSION_PATTERN.matcher(version);
            if (!m.matches()) {
                throw new IllegalArgumentException(
                        "Invalid version: expected a string matching " + VERSION_PATTERN
                                + ", got " + version);
            }

            mMajorVersion = Integer.parseInt(m.group(1));
            mMinorVersion = Integer.parseInt(m.group(2));
            mBuildVersion = Integer.parseInt(m.group(3));
            mPatchVersion = Integer.parseInt(m.group(4));
        }

        /** @return the MAJOR version of cronet used for the traffic */
        public int getMajorVersion() {
            return mMajorVersion;
        }

        /** @return the MINOR version of cronet used for the traffic */
        public int getMinorVersion() {
            return mMinorVersion;
        }

        /** @return the BUILD version of cronet used for the traffic */
        public int getBuildVersion() {
            return mBuildVersion;
        }

        /** @return the PATCH version of cronet used for the traffic */
        public int getPatchVersion() {
            return mPatchVersion;
        }

        @Override
        public String toString() {
            return ""
                    + mMajorVersion
                    + "."
                    + mMinorVersion
                    + "."
                    + mBuildVersion
                    + "."
                    + mPatchVersion;
        }
    }
}
