/*
 * Copyright (C) 2019 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.car.stats;

import android.annotation.Nullable;
import android.car.vms.VmsLayer;
import android.util.ArrayMap;
import android.util.SparseArray;

import com.android.car.CarStatsLog;
import com.android.internal.annotations.GuardedBy;

import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Logger for per-client VMS statistics.
 */
public class VmsClientLogger {
    /**
     * Constants used for identifying client connection states.
     */
    public static class ConnectionState {
        // Attempting to connect to the client
        public static final int CONNECTING =
                CarStatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__CONNECTING;
        // Client connection established
        public static final int CONNECTED =
                CarStatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__CONNECTED;
        // Client connection closed unexpectedly
        public static final int DISCONNECTED =
                CarStatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__DISCONNECTED;
        // Client connection closed by VMS
        public static final int TERMINATED =
                CarStatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__TERMINATED;
        // Error establishing the client connection
        public static final int CONNECTION_ERROR =
                CarStatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED__STATE__CONNECTION_ERROR;
    }

    private final Object mLock = new Object();

    private final int mUid;
    private final String mPackageName;

    @GuardedBy("mLock")
    private SparseArray<AtomicLong> mConnectionStateCounters = new SparseArray<>();

    @GuardedBy("mLock")
    private final ArrayMap<VmsLayer, VmsClientStats> mLayerStats = new ArrayMap<>();

    VmsClientLogger(int clientUid, @Nullable String clientPackage) {
        mUid = clientUid;
        mPackageName = clientPackage != null ? clientPackage : "";
    }

    public int getUid() {
        return mUid;
    }

    public String getPackageName() {
        return mPackageName;
    }

    /**
     * Logs a connection state change for the client.
     *
     * @param connectionState New connection state
     */
    public void logConnectionState(int connectionState) {
        CarStatsLog.write(CarStatsLog.VMS_CLIENT_CONNECTION_STATE_CHANGED,
                mUid, connectionState);

        AtomicLong counter;
        synchronized (mLock) {
            if (!mConnectionStateCounters.contains(connectionState)) {
                mConnectionStateCounters.put(connectionState, new AtomicLong());
            }
            counter = mConnectionStateCounters.get(connectionState);
        }
        counter.incrementAndGet();
    }

    long getConnectionStateCount(int connectionState) {
        AtomicLong counter;
        synchronized (mLock) {
            counter = mConnectionStateCounters.get(connectionState);
        }
        return counter == null ? 0L : counter.get();
    }

    /**
     * Logs that a packet was published by the client.
     *
     * @param layer Layer of packet
     * @param size Size of packet
     */
    public void logPacketSent(VmsLayer layer, long size) {
        getLayerEntry(layer).packetSent(size);
    }

    /**
     * Logs that a packet was received successfully by the client.
     *
     * @param layer Layer of packet
     * @param size Size of packet
     */
    public void logPacketReceived(VmsLayer layer, long size) {
        getLayerEntry(layer).packetReceived(size);
    }

    /**
     * Logs that a packet was dropped due to an error delivering to the client.
     *
     * @param layer Layer of packet
     * @param size Size of packet
     */
    public void logPacketDropped(VmsLayer layer, long size) {
        getLayerEntry(layer).packetDropped(size);
    }

    Collection<VmsClientStats> getLayerEntries() {
        synchronized (mLock) {
            ArrayList<VmsClientStats> layerEntries = new ArrayList<>(mLayerStats.size());
            for (int index = 0; index < mLayerStats.size(); index++) {
                layerEntries.add(new VmsClientStats(mLayerStats.valueAt(index)));
            }
            return layerEntries;
        }
    }

    private VmsClientStats getLayerEntry(VmsLayer layer) {
        synchronized (mLock) {
            return mLayerStats.computeIfAbsent(
                    layer,
                    (k) -> new VmsClientStats(mUid, layer));
        }
    }
}
