/*
 * Copyright (C) 2016 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 android.os.health;

import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArrayMap;

import java.util.Map;

/**
 * Class to write the health stats data into a parcel, so it can then be
 * retrieved via a {@link HealthStats} object.
 *
 * There is an attempt to keep this class as low overhead as possible, for
 * example storing an int[] and a long[] instead of a TimerStat[].
 *
 * @hide
 */
@TestApi
public class HealthStatsWriter {
    private final HealthKeys.Constants mConstants;

    // TimerStat fields
    private final boolean[] mTimerFields;
    private final int[] mTimerCounts;
    private final long[] mTimerTimes;

    // Measurement fields
    private final boolean[] mMeasurementFields;
    private final long[] mMeasurementValues;

    // Stats fields
    private final ArrayMap<String,HealthStatsWriter>[] mStatsValues;

    // Timers fields
    private final ArrayMap<String,TimerStat>[] mTimersValues;

    // Measurements fields
    private final ArrayMap<String,Long>[] mMeasurementsValues;

    /**
     * Construct a HealthStatsWriter object with the given constants.
     *
     * The "getDataType()" of the resulting HealthStats object will be the
     * short name of the java class that the Constants object was initialized
     * with.
     */
    public HealthStatsWriter(HealthKeys.Constants constants) {
        mConstants = constants;

        // TimerStat
        final int timerCount = constants.getSize(HealthKeys.TYPE_TIMER);
        mTimerFields = new boolean[timerCount];
        mTimerCounts = new int[timerCount];
        mTimerTimes = new long[timerCount];

        // Measurement
        final int measurementCount = constants.getSize(HealthKeys.TYPE_MEASUREMENT);
        mMeasurementFields = new boolean[measurementCount];
        mMeasurementValues = new long[measurementCount];

        // Stats
        final int statsCount = constants.getSize(HealthKeys.TYPE_STATS);
        mStatsValues = new ArrayMap[statsCount];

        // Timers
        final int timersCount = constants.getSize(HealthKeys.TYPE_TIMERS);
        mTimersValues = new ArrayMap[timersCount];

        // Measurements
        final int measurementsCount = constants.getSize(HealthKeys.TYPE_MEASUREMENTS);
        mMeasurementsValues = new ArrayMap[measurementsCount];
    }

    /**
     * Add a timer for the given key.
     */
    public void addTimer(int timerId, int count, long time) {
        final int index = mConstants.getIndex(HealthKeys.TYPE_TIMER, timerId);

        mTimerFields[index] = true;
        mTimerCounts[index] = count;
        mTimerTimes[index] = time;
    }

    /**
     * Add a measurement for the given key.
     */
    public void addMeasurement(int measurementId, long value) {
        final int index = mConstants.getIndex(HealthKeys.TYPE_MEASUREMENT, measurementId);

        mMeasurementFields[index] = true;
        mMeasurementValues[index] = value;
    }

    /**
     * Add a recursive HealthStats object for the given key and string name. The value
     * is stored as a HealthStatsWriter until this object is written to a parcel, so
     * don't attempt to reuse the HealthStatsWriter.
     *
     * The value field should not be null.
     */
    public void addStats(int key, String name, HealthStatsWriter value) {
        final int index = mConstants.getIndex(HealthKeys.TYPE_STATS, key);

        ArrayMap<String,HealthStatsWriter> map = mStatsValues[index];
        if (map == null) {
            map = mStatsValues[index] = new ArrayMap<String,HealthStatsWriter>(1);
        }
        map.put(name, value);
    }

    /**
     * Add a TimerStat for the given key and string name.
     *
     * The value field should not be null.
     */
    public void addTimers(int key, String name, TimerStat value) {
        final int index = mConstants.getIndex(HealthKeys.TYPE_TIMERS, key);

        ArrayMap<String,TimerStat> map = mTimersValues[index];
        if (map == null) {
            map = mTimersValues[index] = new ArrayMap<String,TimerStat>(1);
        }
        map.put(name, value);
    }

    /**
     * Add a measurement for the given key and string name.
     */
    public void addMeasurements(int key, String name, long value) {
        final int index = mConstants.getIndex(HealthKeys.TYPE_MEASUREMENTS, key);

        ArrayMap<String,Long> map = mMeasurementsValues[index];
        if (map == null) {
            map = mMeasurementsValues[index] = new ArrayMap<String,Long>(1);
        }
        map.put(name, value);
    }

    /**
     * Flattens the data in this HealthStatsWriter to the Parcel format
     * that can be unparceled into a HealthStat.
     * @more
     * (Called flattenToParcel because this HealthStatsWriter itself is
     * not parcelable and we don't flatten all the business about the
     * HealthKeys.Constants, only the values that were actually supplied)
     */
    public void flattenToParcel(Parcel out) {
        int[] keys;

        // Header fields
        out.writeString(mConstants.getDataType());

        // TimerStat fields
        out.writeInt(countBooleanArray(mTimerFields));
        keys = mConstants.getKeys(HealthKeys.TYPE_TIMER);
        for (int i=0; i<keys.length; i++) {
            if (mTimerFields[i]) {
                out.writeInt(keys[i]);
                out.writeInt(mTimerCounts[i]);
                out.writeLong(mTimerTimes[i]);
            }
        }

        // Measurement fields
        out.writeInt(countBooleanArray(mMeasurementFields));
        keys = mConstants.getKeys(HealthKeys.TYPE_MEASUREMENT);
        for (int i=0; i<keys.length; i++) {
            if (mMeasurementFields[i]) {
                out.writeInt(keys[i]);
                out.writeLong(mMeasurementValues[i]);
            }
        }

        // Stats
        out.writeInt(countObjectArray(mStatsValues));
        keys = mConstants.getKeys(HealthKeys.TYPE_STATS);
        for (int i=0; i<keys.length; i++) {
            if (mStatsValues[i] != null) {
                out.writeInt(keys[i]);
                writeHealthStatsWriterMap(out, mStatsValues[i]);
            }
        }

        // Timers
        out.writeInt(countObjectArray(mTimersValues));
        keys = mConstants.getKeys(HealthKeys.TYPE_TIMERS);
        for (int i=0; i<keys.length; i++) {
            if (mTimersValues[i] != null) {
                out.writeInt(keys[i]);
                writeParcelableMap(out, mTimersValues[i]);
            }
        }

        // Measurements
        out.writeInt(countObjectArray(mMeasurementsValues));
        keys = mConstants.getKeys(HealthKeys.TYPE_MEASUREMENTS);
        for (int i=0; i<keys.length; i++) {
            if (mMeasurementsValues[i] != null) {
                out.writeInt(keys[i]);
                writeLongsMap(out, mMeasurementsValues[i]);
            }
        }
    }

    /**
     * Count how many of the fields have been set.
     */
    private static int countBooleanArray(boolean[] fields) {
        int count = 0;
        final int N = fields.length;
        for (int i=0; i<N; i++) {
            if (fields[i]) {
                count++;
            }
        }
        return count;
    }

    /**
     * Count how many of the fields have been set.
     */
    private static <T extends Object> int countObjectArray(T[] fields) {
        int count = 0;
        final int N = fields.length;
        for (int i=0; i<N; i++) {
            if (fields[i] != null) {
                count++;
            }
        }
        return count;
    }

    /**
     * Write a map of String to HealthStatsWriter to the Parcel.
     */
    private static void writeHealthStatsWriterMap(Parcel out,
            ArrayMap<String,HealthStatsWriter> map) {
        final int N = map.size();
        out.writeInt(N);
        for (int i=0; i<N; i++) {
            out.writeString(map.keyAt(i));
            map.valueAt(i).flattenToParcel(out);
        }
    }

    /**
     * Write a map of String to Parcelables to the Parcel.
     */
    private static <T extends Parcelable> void writeParcelableMap(Parcel out,
            ArrayMap<String,T> map) {
        final int N = map.size();
        out.writeInt(N);
        for (int i=0; i<N; i++) {
            out.writeString(map.keyAt(i));
            map.valueAt(i).writeToParcel(out, 0);
        }
    }

    /**
     * Write a map of String to Longs to the Parcel.
     */
    private static void writeLongsMap(Parcel out, ArrayMap<String,Long> map) {
        final int N = map.size();
        out.writeInt(N);
        for (int i=0; i<N; i++) {
            out.writeString(map.keyAt(i));
            out.writeLong(map.valueAt(i));
        }
    }
}


