/*
 * Copyright (C) 2013 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.internal.app.procstats;

import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_AVERAGE;
import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_MAXIMUM;
import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_MINIMUM;
import static com.android.internal.app.procstats.ProcessStats.PSS_SAMPLE_COUNT;
import static com.android.internal.app.procstats.ProcessStats.PSS_MINIMUM;
import static com.android.internal.app.procstats.ProcessStats.PSS_AVERAGE;
import static com.android.internal.app.procstats.ProcessStats.PSS_MAXIMUM;
import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MINIMUM;
import static com.android.internal.app.procstats.ProcessStats.PSS_USS_AVERAGE;
import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MAXIMUM;
import static com.android.internal.app.procstats.ProcessStats.PSS_COUNT;

import android.service.procstats.ProcessStatsStateProto;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoUtils;

/**
 * Class to accumulate PSS data.
 */
public class PssTable extends SparseMappingTable.Table {
    /**
     * Construct the PssTable with 'tableData' as backing store
     * for the longs data.
     */
    public PssTable(SparseMappingTable tableData) {
        super(tableData);
    }

    /**
     * Merge the the values from the other table into this one.
     */
    public void mergeStats(PssTable that) {
        final int N = that.getKeyCount();
        for (int i=0; i<N; i++) {
            final int thatKey = that.getKeyAt(i);
            final int state = SparseMappingTable.getIdFromKey(thatKey);

            final int key = getOrAddKey((byte)state, PSS_COUNT);
            final long[] stats = getArrayForKey(key);
            final int statsIndex = SparseMappingTable.getIndexFromKey(key);

            final long[] thatStats = that.getArrayForKey(thatKey);
            final int thatStatsIndex = SparseMappingTable.getIndexFromKey(thatKey);

            mergeStats(stats, statsIndex, thatStats, thatStatsIndex);
        }
    }

    /**
     * Merge the supplied PSS data in.  The new min pss will be the minimum of the existing
     * one and the new one, the average will now incorporate the new average, etc.
     */
    public void mergeStats(int state, int inCount, long minPss, long avgPss, long maxPss,
            long minUss, long avgUss, long maxUss, long minRss, long avgRss, long maxRss) {
        final int key = getOrAddKey((byte)state, PSS_COUNT);
        final long[] stats = getArrayForKey(key);
        final int statsIndex = SparseMappingTable.getIndexFromKey(key);
        mergeStats(stats, statsIndex, inCount, minPss, avgPss, maxPss, minUss, avgUss, maxUss,
                minRss, avgRss, maxRss);
    }

    public static void mergeStats(final long[] stats, final int statsIndex,
            final long[] thatStats, int thatStatsIndex) {
        mergeStats(stats, statsIndex, (int)thatStats[thatStatsIndex + PSS_SAMPLE_COUNT],
                thatStats[thatStatsIndex + PSS_MINIMUM],
                thatStats[thatStatsIndex + PSS_AVERAGE],
                thatStats[thatStatsIndex + PSS_MAXIMUM],
                thatStats[thatStatsIndex + PSS_USS_MINIMUM],
                thatStats[thatStatsIndex + PSS_USS_AVERAGE],
                thatStats[thatStatsIndex + PSS_USS_MAXIMUM],
                thatStats[thatStatsIndex + PSS_RSS_MINIMUM],
                thatStats[thatStatsIndex + PSS_RSS_AVERAGE],
                thatStats[thatStatsIndex + PSS_RSS_MAXIMUM]);
    }

    public static void mergeStats(final long[] stats, final int statsIndex, final int inCount,
            final long minPss, final long avgPss, final long maxPss,
            final long minUss, final long avgUss, final long maxUss,
            final long minRss, final long avgRss, final long maxRss) {
        final long count = stats[statsIndex + PSS_SAMPLE_COUNT];
        if (count == 0) {
            stats[statsIndex + PSS_SAMPLE_COUNT] = inCount;
            stats[statsIndex + PSS_MINIMUM] = minPss;
            stats[statsIndex + PSS_AVERAGE] = avgPss;
            stats[statsIndex + PSS_MAXIMUM] = maxPss;
            stats[statsIndex + PSS_USS_MINIMUM] = minUss;
            stats[statsIndex + PSS_USS_AVERAGE] = avgUss;
            stats[statsIndex + PSS_USS_MAXIMUM] = maxUss;
            stats[statsIndex + PSS_RSS_MINIMUM] = minRss;
            stats[statsIndex + PSS_RSS_AVERAGE] = avgRss;
            stats[statsIndex + PSS_RSS_MAXIMUM] = maxRss;
        } else {
            stats[statsIndex + PSS_SAMPLE_COUNT] = count + inCount;

            if (stats[statsIndex + PSS_MINIMUM] > minPss) {
                stats[statsIndex + PSS_MINIMUM] = minPss;
            }

            stats[statsIndex + PSS_AVERAGE] = (long)(((stats[statsIndex + PSS_AVERAGE]
                    * (double)count) + (avgPss * (double)inCount)) / (count + inCount));

            if (stats[statsIndex + PSS_MAXIMUM] < maxPss) {
                stats[statsIndex + PSS_MAXIMUM] = maxPss;
            }

            if (stats[statsIndex + PSS_USS_MINIMUM] > minUss) {
                stats[statsIndex + PSS_USS_MINIMUM] = minUss;
            }

            stats[statsIndex + PSS_USS_AVERAGE] = (long)(((stats[statsIndex + PSS_USS_AVERAGE]
                    * (double)count) + (avgUss * (double)inCount)) / (count + inCount));

            if (stats[statsIndex + PSS_USS_MAXIMUM] < maxUss) {
                stats[statsIndex + PSS_USS_MAXIMUM] = maxUss;
            }

            if (stats[statsIndex + PSS_RSS_MINIMUM] > minRss) {
                stats[statsIndex + PSS_RSS_MINIMUM] = minRss;
            }

            stats[statsIndex + PSS_RSS_AVERAGE] = (long)(((stats[statsIndex + PSS_RSS_AVERAGE]
                    * (double)count) + (avgRss * (double)inCount)) / (count + inCount));

            if (stats[statsIndex + PSS_RSS_MAXIMUM] < maxRss) {
                stats[statsIndex + PSS_RSS_MAXIMUM] = maxRss;
            }
        }
    }

    public void writeStatsToProtoForKey(ProtoOutputStream proto, int key) {
        final long[] stats = getArrayForKey(key);
        final int statsIndex = SparseMappingTable.getIndexFromKey(key);
        writeStatsToProto(proto, stats, statsIndex);
    }

    public static void writeStatsToProto(ProtoOutputStream proto, final long[] stats,
            final int statsIndex) {
        proto.write(ProcessStatsStateProto.SAMPLE_SIZE, stats[statsIndex + PSS_SAMPLE_COUNT]);
        ProtoUtils.toAggStatsProto(proto, ProcessStatsStateProto.PSS,
                stats[statsIndex + PSS_MINIMUM],
                stats[statsIndex + PSS_AVERAGE],
                stats[statsIndex + PSS_MAXIMUM]);
        ProtoUtils.toAggStatsProto(proto, ProcessStatsStateProto.USS,
                stats[statsIndex + PSS_USS_MINIMUM],
                stats[statsIndex + PSS_USS_AVERAGE],
                stats[statsIndex + PSS_USS_MAXIMUM]);
        ProtoUtils.toAggStatsProto(proto, ProcessStatsStateProto.RSS,
                stats[statsIndex + PSS_RSS_MINIMUM],
                stats[statsIndex + PSS_RSS_AVERAGE],
                stats[statsIndex + PSS_RSS_MAXIMUM]);
    }
}
