/*
 * 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.
 */

#ifndef ANDROID_MEDIA_MEDIAMETRICSITEM_H
#define ANDROID_MEDIA_MEDIAMETRICSITEM_H

#include "MediaMetrics.h"
#include "MediaMetricsConstants.h"

#include <algorithm>
#include <map>
#include <string>
#include <sys/types.h>
#include <variant>

#include <binder/Parcel.h>
#include <log/log.h>
#include <utils/Errors.h>
#include <utils/Timers.h> // nsecs_t

namespace android {

namespace media { class IMediaMetricsService; }

class Parcel;

/*
 * MediaMetrics Item
 *
 * The MediaMetrics Item allows get/set operations and recording to the service.
 *
 * The MediaMetrics LogItem is a faster logging variant. It allows set operations only,
 * and then recording to the service.
 *
 * The Byte String format is as follows:
 *
 * For Java
 *  int64 corresponds to long
 *  int32, uint32 corresponds to int
 *  uint16 corresponds to char
 *  uint8, int8 corresponds to byte
 *
 * For items transmitted from Java, uint8 and uint32 values are limited
 * to INT8_MAX and INT32_MAX.  This constrains the size of large items
 * to 2GB, which is consistent with ByteBuffer max size. A native item
 * can conceivably have size of 4GB.
 *
 * Physical layout of integers and doubles within the MediaMetrics byte string
 * is in Native / host order, which is usually little endian.
 *
 * Note that primitive data (ints, doubles) within a Byte String has
 * no extra padding or alignment requirements, like ByteBuffer.
 *
 * -- begin of item
 * -- begin of header
 * (uint32) item size: including the item size field
 * (uint32) header size, including the item size and header size fields.
 * (uint16) version: exactly 0
 * (uint16) key size, that is key strlen + 1 for zero termination.
 * (int8)+ key, a string which is 0 terminated (UTF-8).
 * (int32) pid
 * (int32) uid
 * (int64) timestamp
 * -- end of header
 * -- begin body
 * (uint32) number of properties
 * -- repeat for number of properties
 *     (uint16) property size, including property size field itself
 *     (uint8) type of property
 *     (int8)+ key string, including 0 termination
 *      based on type of property (given above), one of:
 *       (int32)
 *       (int64)
 *       (double)
 *       (int8)+ for TYPE_CSTRING, including 0 termination
 *       (int64, int64) for rate
 * -- end body
 * -- end of item
 *
 * The Byte String format must match MediaMetrics.java.
 */

namespace mediametrics {

// Type must match MediaMetrics.java
enum Type {
    kTypeNone = 0,
    kTypeInt32 = 1,
    kTypeInt64 = 2,
    kTypeDouble = 3,
    kTypeCString = 4,
    kTypeRate = 5,
};

/*
 * Helper for status conversions
 */

inline constexpr const char* statusToStatusString(status_t status) {
    switch (status) {
    case BAD_VALUE:
        return AMEDIAMETRICS_PROP_STATUS_VALUE_ARGUMENT;
    case DEAD_OBJECT:
    case FAILED_TRANSACTION:
        return AMEDIAMETRICS_PROP_STATUS_VALUE_IO;
    case NO_MEMORY:
        return AMEDIAMETRICS_PROP_STATUS_VALUE_MEMORY;
    case PERMISSION_DENIED:
        return AMEDIAMETRICS_PROP_STATUS_VALUE_SECURITY;
    case NO_INIT:
    case INVALID_OPERATION:
        return AMEDIAMETRICS_PROP_STATUS_VALUE_STATE;
    case WOULD_BLOCK:
        return AMEDIAMETRICS_PROP_STATUS_VALUE_TIMEOUT;
    default:
        if (status >= 0) return AMEDIAMETRICS_PROP_STATUS_VALUE_OK; // non-negative values "OK"
        [[fallthrough]];            // negative values are error.
    case UNKNOWN_ERROR:
        return AMEDIAMETRICS_PROP_STATUS_VALUE_UNKNOWN;
    }
}

status_t statusStringToStatus(const char *error);

/*
 * Time printing
 *
 * kPrintFormatLong time string is 19 characters (including null termination).
 * Example Long Form: "03-27 16:47:06.187"
 *                     MM DD HH MM SS MS
 *
 * kPrintFormatShort time string is 13 characters (including null termination).
 * Example Short Form: "16:47:06.187"
 *                      HH MM SS MS
 */

enum PrintFormat {
    kPrintFormatLong = 0,
    kPrintFormatShort = 1,
};

/**
 * Converts real time in ns to a time string object, with format similar to logcat.
 *
 * \param ns         input real time in nanoseconds to convert.
 * \param buffer     the buffer location to put the converted string.
 * \param bufferSize the size of buffer in bytes.
 * \param format     format, from enum PrintFormat.
 */
void nsToString(
        int64_t ns, char *buffer, size_t bufferSize, PrintFormat format = kPrintFormatLong);

// Contains the time string
struct time_string_t {
    char time[19]; /* minimum size buffer */
};

/**
 * Converts real time in ns to a time string object, with format similar to logcat.
 *
 * \param ns     input real time in nanoseconds to convert.
 * \param format format, from enum PrintFormat.
 * \return       a time_string_t object with the time string encoded.
 */
static inline time_string_t timeStringFromNs(int64_t ns, PrintFormat format = kPrintFormatLong) {
    time_string_t ts;
    nsToString(ns, ts.time, sizeof(ts.time), format);
    return ts;
}

/**
 * Finds the end of the common time prefix.
 *
 * This is as an option to remove the common time prefix to avoid
 * unnecessary duplicated strings.
 *
 * \param time1 a time string from timeStringFromNs
 * \param time2 a time string from timeStringFromNs
 * \return      the position where the common time prefix ends. For abbreviated
 *              printing of time2, offset the character pointer by this position.
 */
static inline size_t commonTimePrefixPosition(const char *time1, const char *time2) {
    size_t i;

    // Find location of the first mismatch between strings
    for (i = 0; ; ++i) {
        if (time1[i] != time2[i]) {
            break;
        }
        if (time1[i] == 0) {
            return i; // strings match completely
        }
    }

    // Go backwards until we find a delimeter or space.
    for (; i > 0
           && isdigit(time1[i]) // still a number
           && time1[i - 1] != ' '
         ; --i) {
    }
    return i;
}

/**
 * The MediaMetrics Item has special Item properties,
 * derived internally or through dedicated setters.
 *
 * For consistency we use the following keys to represent
 * these special Item properties when in a generic Bundle
 * or in a std::map.
 *
 * These values must match MediaMetrics.java
 */
static inline constexpr const char *BUNDLE_TOTAL_SIZE = "_totalSize";
static inline constexpr const char *BUNDLE_HEADER_SIZE = "_headerSize";
static inline constexpr const char *BUNDLE_VERSION = "_version";
static inline constexpr const char *BUNDLE_KEY_SIZE = "_keySize";
static inline constexpr const char *BUNDLE_KEY = "_key";
static inline constexpr const char *BUNDLE_PID = "_pid";
static inline constexpr const char *BUNDLE_UID = "_uid";
static inline constexpr const char *BUNDLE_TIMESTAMP = "_timestamp";
static inline constexpr const char *BUNDLE_PROPERTY_COUNT = "_propertyCount";

template<size_t N>
static inline bool startsWith(const std::string &s, const char (&comp)[N]) {
    return !strncmp(s.c_str(), comp, N - 1); // last char is null termination
}

static inline bool startsWith(const std::string& s, const std::string& comp) {
    return !strncmp(s.c_str(), comp.c_str(), comp.size());
}

/**
 * Defers a function to run in the destructor.
 *
 * This helper class is used to log results on exit of a method.
 */
class Defer {
public:
    template <typename U>
    explicit Defer(U &&f) : mThunk(std::forward<U>(f)) {}
    ~Defer() { mThunk(); }

private:
    const std::function<void()> mThunk;
};

/**
 * Media Metrics BaseItem
 *
 * A base class which contains utility static functions to write to a byte stream
 * and access the Media Metrics service.
 */

class BaseItem {
    friend class MediaMetricsDeathNotifier; // for dropInstance
    // enabled 1, disabled 0
public:
    // are we collecting metrics data
    static bool isEnabled();
    // returns the MediaMetrics service if active.
    static sp<media::IMediaMetricsService> getService();
    // submits a raw buffer directly to the MediaMetrics service - this is highly optimized.
    static status_t submitBuffer(const char *buffer, size_t len);

protected:
    static constexpr const char * const EnabledProperty = "media.metrics.enabled";
    static constexpr const char * const EnabledPropertyPersist = "persist.media.metrics.enabled";
    static const int EnabledProperty_default = 1;

    // let's reuse a binder connection
    static sp<media::IMediaMetricsService> sMediaMetricsService;

    static void dropInstance();

    template <typename T>
    struct is_item_type {
        static constexpr inline bool value =
             std::is_same<T, int32_t>::value
             || std::is_same<T, int64_t>::value
             || std::is_same<T, double>::value
             || std::is_same<T, std::pair<int64_t, int64_t>>:: value
             || std::is_same<T, std::string>::value
             || std::is_same<T, std::monostate>::value;
    };

    template <typename T>
    struct get_type_of {
        static_assert(is_item_type<T>::value);
        static constexpr inline Type value =
             std::is_same<T, int32_t>::value ? kTypeInt32
             : std::is_same<T, int64_t>::value ? kTypeInt64
             : std::is_same<T, double>::value ? kTypeDouble
             : std::is_same<T, std::pair<int64_t, int64_t>>:: value ? kTypeRate
             : std::is_same<T, std::string>::value ? kTypeCString
             : std::is_same<T, std::monostate>::value ? kTypeNone
         : kTypeNone;
    };

    template <typename T>
    static size_t sizeOfByteString(const char *name, const T& value) {
        static_assert(is_item_type<T>::value);
        return 2 + 1 + strlen(name) + 1 + sizeof(value);
    }
    template <> // static
    size_t sizeOfByteString(const char *name, const std::string& value) {
        return 2 + 1 + strlen(name) + 1 + value.size() + 1;
    }
    template <> // static
    size_t sizeOfByteString(const char *name, const std::monostate&) {
         return 2 + 1 + strlen(name) + 1;
    }
    // for speed
    static size_t sizeOfByteString(const char *name, const char *value) {
        return 2 + 1 + strlen(name) + 1 + strlen(value) + 1;
    }

    template <typename T>
    static status_t insert(const T& val, char **bufferpptr, char *bufferptrmax) {
        static_assert(std::is_trivially_constructible<T>::value);
        const size_t size = sizeof(val);
        if (*bufferpptr + size > bufferptrmax) {
            ALOGE("%s: buffer exceeded with size %zu", __func__, size);
            return BAD_VALUE;
        }
        memcpy(*bufferpptr, &val, size);
        *bufferpptr += size;
        return NO_ERROR;
    }
    template <> // static
    status_t insert(const std::string& val, char **bufferpptr, char *bufferptrmax) {
        const size_t size = val.size() + 1;
        if (size > UINT16_MAX || *bufferpptr + size > bufferptrmax) {
            ALOGE("%s: buffer exceeded with size %zu", __func__, size);
            return BAD_VALUE;
        }
        memcpy(*bufferpptr, val.c_str(), size);
        *bufferpptr += size;
        return NO_ERROR;
    }
    template <> // static
    status_t insert(const std::pair<int64_t, int64_t>& val,
            char **bufferpptr, char *bufferptrmax) {
        const size_t size = sizeof(val.first) + sizeof(val.second);
        if (*bufferpptr + size > bufferptrmax) {
            ALOGE("%s: buffer exceeded with size %zu", __func__, size);
            return BAD_VALUE;
        }
        memcpy(*bufferpptr, &val.first, sizeof(val.first));
        memcpy(*bufferpptr + sizeof(val.first), &val.second, sizeof(val.second));
        *bufferpptr += size;
        return NO_ERROR;
    }
    template <> // static
    status_t insert(const std::monostate&, char **, char *) {
        return NO_ERROR;
    }
    // for speed
    static status_t insert(const char *val, char **bufferpptr, char *bufferptrmax) {
        const size_t size = strlen(val) + 1;
        if (size > UINT16_MAX || *bufferpptr + size > bufferptrmax) {
            ALOGE("%s: buffer exceeded with size %zu", __func__, size);
            return BAD_VALUE;
        }
        memcpy(*bufferpptr, val, size);
        *bufferpptr += size;
        return NO_ERROR;
    }

    template <typename T>
    static status_t writeToByteString(
            const char *name, const T& value, char **bufferpptr, char *bufferptrmax) {
        static_assert(is_item_type<T>::value);
        const size_t len = sizeOfByteString(name, value);
        if (len > UINT16_MAX) return BAD_VALUE;
        return insert((uint16_t)len, bufferpptr, bufferptrmax)
                ?: insert((uint8_t)get_type_of<T>::value, bufferpptr, bufferptrmax)
                ?: insert(name, bufferpptr, bufferptrmax)
                ?: insert(value, bufferpptr, bufferptrmax);
    }
    // for speed
    static status_t writeToByteString(
            const char *name, const char *value, char **bufferpptr, char *bufferptrmax) {
        const size_t len = sizeOfByteString(name, value);
        if (len > UINT16_MAX) return BAD_VALUE;
        return insert((uint16_t)len, bufferpptr, bufferptrmax)
                ?: insert((uint8_t)kTypeCString, bufferpptr, bufferptrmax)
                ?: insert(name, bufferpptr, bufferptrmax)
                ?: insert(value, bufferpptr, bufferptrmax);
    }

    template <typename T>
    static void toStringBuffer(
            const char *name, const T& value, char *buffer, size_t length) = delete;
    template <> // static
    void toStringBuffer(
            const char *name, const int32_t& value, char *buffer, size_t length) {
        snprintf(buffer, length, "%s=%d", name, value);
    }
    template <> // static
    void toStringBuffer(
            const char *name, const int64_t& value, char *buffer, size_t length) {
        snprintf(buffer, length, "%s=%lld", name, (long long)value);
    }
    template <> // static
    void toStringBuffer(
            const char *name, const double& value, char *buffer, size_t length) {
        snprintf(buffer, length, "%s=%e", name, value);
    }
    template <> // static
    void toStringBuffer(
            const char *name, const std::pair<int64_t, int64_t>& value,
            char *buffer, size_t length) {
        snprintf(buffer, length, "%s=%lld/%lld",
                name, (long long)value.first, (long long)value.second);
    }
    template <> // static
    void toStringBuffer(
            const char *name, const std::string& value, char *buffer, size_t length) {
        // TODO sanitize string for ':' '='
        snprintf(buffer, length, "%s=%s", name, value.c_str());
    }
    template <> // static
    void toStringBuffer(
            const char *name, const std::monostate&, char *buffer, size_t length) {
        snprintf(buffer, length, "%s=()", name);
    }

    template <typename T>
    static status_t writeToParcel(
            const char *name, const T& value, Parcel *parcel) = delete;
    template <> // static
    status_t writeToParcel(
            const char *name, const int32_t& value, Parcel *parcel) {
        return parcel->writeCString(name)
               ?: parcel->writeInt32(get_type_of<int32_t>::value)
               ?: parcel->writeInt32(value);
    }
    template <> // static
    status_t writeToParcel(
            const char *name, const int64_t& value, Parcel *parcel) {
        return parcel->writeCString(name)
               ?: parcel->writeInt32(get_type_of<int64_t>::value)
               ?: parcel->writeInt64(value);
    }
    template <> // static
    status_t writeToParcel(
            const char *name, const double& value, Parcel *parcel) {
        return parcel->writeCString(name)
               ?: parcel->writeInt32(get_type_of<double>::value)
               ?: parcel->writeDouble(value);
    }
    template <> // static
    status_t writeToParcel(
            const char *name, const std::pair<int64_t, int64_t>& value, Parcel *parcel) {
        return parcel->writeCString(name)
               ?: parcel->writeInt32(get_type_of< std::pair<int64_t, int64_t>>::value)
               ?: parcel->writeInt64(value.first)
               ?: parcel->writeInt64(value.second);
    }
    template <> // static
    status_t writeToParcel(
            const char *name, const std::string& value, Parcel *parcel) {
        return parcel->writeCString(name)
               ?: parcel->writeInt32(get_type_of<std::string>::value)
               ?: parcel->writeCString(value.c_str());
    }
    template <> // static
    status_t writeToParcel(
            const char *name, const std::monostate&, Parcel *parcel) {
        return parcel->writeCString(name)
               ?: parcel->writeInt32(get_type_of<std::monostate>::value);
    }

    template <typename T>
    static status_t extract(T *val, const char **bufferpptr, const char *bufferptrmax) {
        static_assert(std::is_trivially_constructible<T>::value);
        const size_t size = sizeof(*val);
        if (*bufferpptr + size > bufferptrmax) {
            ALOGE("%s: buffer exceeded with size %zu", __func__, size);
            return BAD_VALUE;
        }
        memcpy(val, *bufferpptr, size);
        *bufferpptr += size;
        return NO_ERROR;
    }
    template <> // static
    status_t extract(std::string *val, const char **bufferpptr, const char *bufferptrmax) {
        const char *ptr = *bufferpptr;
        do {
            if (ptr >= bufferptrmax) {
                ALOGE("%s: buffer exceeded", __func__);
                android_errorWriteLog(0x534e4554, "204445255");
                return BAD_VALUE;
            }
        } while (*ptr++ != 0);
        // ptr is terminator+1, == bufferptrmax if we finished entire buffer
        *val = *bufferpptr;
        *bufferpptr = ptr;
        return NO_ERROR;
    }
    template <> // static
    status_t extract(std::pair<int64_t, int64_t> *val,
            const char **bufferpptr, const char *bufferptrmax) {
        const size_t size = sizeof(val->first) + sizeof(val->second);
        if (*bufferpptr + size > bufferptrmax) {
            ALOGE("%s: buffer exceeded with size %zu", __func__, size);
            return BAD_VALUE;
        }
        memcpy(&val->first, *bufferpptr, sizeof(val->first));
        memcpy(&val->second, *bufferpptr + sizeof(val->first), sizeof(val->second));
        *bufferpptr += size;
        return NO_ERROR;
    }
    template <> // static
    status_t extract(std::monostate *, const char **, const char *) {
        return NO_ERROR;
    }
};

/**
 * Media Metrics BufferedItem
 *
 * A base class which represents a put-only Media Metrics item, storing
 * the Media Metrics data in a buffer with begin and end pointers.
 *
 * If a property key is entered twice, it will be stored in the buffer twice,
 * and (implementation defined) the last value for that key will be used
 * by the Media Metrics service.
 *
 * For realloc, a baseRealloc pointer must be passed in either explicitly
 * or implicitly in the constructor. This will be updated with the value used on realloc.
 */
class BufferedItem : public BaseItem {
public:
    static inline constexpr uint16_t kVersion = 0;

    virtual ~BufferedItem() = default;
    BufferedItem(const BufferedItem&) = delete;
    BufferedItem& operator=(const BufferedItem&) = delete;

    BufferedItem(const std::string& key, char *begin, char *end)
        : BufferedItem(key.c_str(), begin, end) { }

    BufferedItem(const char *key, char *begin, char *end)
        : BufferedItem(key, begin, end, nullptr) { }

    BufferedItem(const char *key, char **begin, char *end)
        : BufferedItem(key, *begin, end, begin) { }

    BufferedItem(const char *key, char *begin, char *end, char **baseRealloc)
        : mBegin(begin)
        , mEnd(end)
        , mBaseRealloc(baseRealloc)
    {
        init(key);
    }

    template<typename T>
    BufferedItem &set(const char *key, const T& value) {
        reallocFor(sizeOfByteString(key, value));
        if (mStatus == NO_ERROR) {
            mStatus = BaseItem::writeToByteString(key, value, &mBptr, mEnd);
            ++mPropCount;
        }
        return *this;
    }

    template<typename T>
    BufferedItem &set(const std::string& key, const T& value) {
        return set(key.c_str(), value);
    }

    BufferedItem &setPid(pid_t pid) {
        if (mStatus == NO_ERROR) {
            copyTo(mBegin + mHeaderLen - 16, (int32_t)pid);
        }
        return *this;
    }

    BufferedItem &setUid(uid_t uid) {
        if (mStatus == NO_ERROR) {
            copyTo(mBegin + mHeaderLen - 12, (int32_t)uid);
        }
        return *this;
    }

    BufferedItem &setTimestamp(nsecs_t timestamp) {
        if (mStatus == NO_ERROR) {
            copyTo(mBegin + mHeaderLen - 8, (int64_t)timestamp);
        }
        return *this;
    }

    bool record() {
        return updateHeader()
                && BaseItem::submitBuffer(getBuffer(), getLength()) == OK;
    }

    bool isValid () const {
        return mStatus == NO_ERROR;
    }

    char *getBuffer() const { return mBegin; }
    size_t getLength() const { return mBptr - mBegin; }
    size_t getRemaining() const { return mEnd - mBptr; }
    size_t getCapacity() const { return mEnd - mBegin; }

    bool updateHeader() {
        if (mStatus != NO_ERROR) return false;
        copyTo(mBegin + 0, (uint32_t)getLength());
        copyTo(mBegin + 4, (uint32_t)mHeaderLen);
        copyTo(mBegin + mHeaderLen, (uint32_t)mPropCount);
        return true;
    }

protected:
    BufferedItem() = default;

    void reallocFor(size_t required) {
        if (mStatus != NO_ERROR) return;
        const size_t remaining = getRemaining();
        if (required <= remaining) return;
        if (mBaseRealloc == nullptr) {
            mStatus = NO_MEMORY;
            return;
        }

        const size_t current = getLength();
        size_t minimum = current + required;
        if (minimum > SSIZE_MAX >> 1) {
            mStatus = NO_MEMORY;
            return;
        }
        minimum <<= 1;
        void *newptr = realloc(*mBaseRealloc, minimum);
        if (newptr == nullptr) {
            mStatus = NO_MEMORY;
            return;
        }
        if (newptr != *mBaseRealloc) {
            // ALOGD("base changed! current:%zu new size %zu", current, minimum);
            if (*mBaseRealloc == nullptr) {
                memcpy(newptr, mBegin, current);
            }
            mBegin = (char *)newptr;
            *mBaseRealloc = mBegin;
            mEnd = mBegin + minimum;
            mBptr = mBegin + current;
        } else {
            // ALOGD("base kept! current:%zu new size %zu", current, minimum);
            mEnd = mBegin + minimum;
        }
    }
    template<typename T>
    void copyTo(char *ptr, const T& value) {
        memcpy(ptr, &value, sizeof(value));
    }

    void init(const char *key) {
        mBptr = mBegin;
        const size_t keylen = key == nullptr ? 0 : strlen(key) + 1;
        if (keylen <= 1) {
            mStatus = BAD_VALUE; // prevent null pointer or empty keys.
            return;
        }
        mHeaderLen = 4 + 4 + 2 + 2 + keylen + 4 + 4 + 8;
        reallocFor(mHeaderLen);
        if (mStatus != NO_ERROR) return;
        mBptr = mBegin + mHeaderLen + 4; // this includes propcount.

        if (mEnd < mBptr || keylen > UINT16_MAX) {
           mStatus = NO_MEMORY;
           mBptr = mEnd;
           return;
        }
        copyTo(mBegin + 8, kVersion);
        copyTo(mBegin + 10, (uint16_t)keylen);
        strcpy(mBegin + 12, key);

        // initialize some parameters (that could be overridden)
        setPid(-1);
        setUid(-1);
        setTimestamp(0);
    }

    char *mBegin = nullptr;
    char *mEnd = nullptr;
    char **mBaseRealloc = nullptr;  // set to an address if realloc should be done.
                                    // upon return, that pointer is updated with
                                    // whatever needs to be freed.
    char *mBptr = nullptr;
    status_t mStatus = NO_ERROR;
    uint32_t mPropCount = 0;
    uint32_t mHeaderLen = 0;
};

/**
 * MediaMetrics LogItem is a stack allocated mediametrics item used for
 * fast logging.  It falls over to a malloc if needed.
 *
 * This is templated with a buffer size to allocate on the stack.
 */
template <size_t N = 4096>
class LogItem : public BufferedItem {
public:
    explicit LogItem(const std::string& key) : LogItem(key.c_str()) { }

    // Since this class will not be defined before the base class, we initialize variables
    // in our own order.
    explicit LogItem(const char *key) {
         mBegin = mBuffer;
         mEnd = mBuffer + N;
         mBaseRealloc = &mReallocPtr;
         init(key);
    }

    ~LogItem() override {
        if (mReallocPtr != nullptr) { // do the check before calling free to avoid overhead.
            free(mReallocPtr);
        }
    }

private:
    char *mReallocPtr = nullptr;  // set non-null by base class if realloc happened.
    char mBuffer[N];
};


/**
 * Media Metrics Item
 *
 * A mutable item representing an event or record that will be
 * logged with the Media Metrics service.  For client logging, one should
 * use the mediametrics::Item.
 *
 * The Item is designed for the service as it has getters.
 */
class Item final : public mediametrics::BaseItem {
public:

    class Prop {
    public:
        using Elem = std::variant<
                std::monostate,               // kTypeNone
                int32_t,                      // kTypeInt32
                int64_t,                      // kTypeInt64
                double,                       // kTypeDouble
                std::string,                  // kTypeCString
                std::pair<int64_t, int64_t>   // kTypeRate
                >;

        Prop() = default;
        Prop(const Prop& other) {
           *this = other;
        }
        Prop& operator=(const Prop& other) {
            mName = other.mName;
            mElem = other.mElem;
            return *this;
        }
        Prop(Prop&& other) noexcept {
            *this = std::move(other);
        }
        Prop& operator=(Prop&& other) noexcept {
            mName = std::move(other.mName);
            mElem = std::move(other.mElem);
            return *this;
        }

        bool operator==(const Prop& other) const {
            return mName == other.mName && mElem == other.mElem;
        }
        bool operator!=(const Prop& other) const {
            return !(*this == other);
        }

        void clear() {
            mName.clear();
            mElem = std::monostate{};
        }
        void clearValue() {
            mElem = std::monostate{};
        }

        const char *getName() const {
            return mName.c_str();
        }

        void swap(Prop& other) {
            std::swap(mName, other.mName);
            std::swap(mElem, other.mElem);
        }

        void setName(const char *name) {
            mName = name;
        }

        bool isNamed(const char *name) const {
            return mName == name;
        }

        template <typename T> void visit(T f) const {
            std::visit(f, mElem);
        }

        template <typename T> bool get(T *value) const {
            auto pval = std::get_if<T>(&mElem);
            if (pval != nullptr) {
                *value = *pval;
                return true;
            }
            return false;
        }

        const Elem& get() const {
            return mElem;
        }

        template <typename T> void set(const T& value) {
            mElem = value;
        }

        template <typename T> void add(const T& value) {
            auto pval = std::get_if<T>(&mElem);
            if (pval != nullptr) {
                *pval += value;
            } else {
                mElem = value;
            }
        }

        template <> void add(const std::pair<int64_t, int64_t>& value) {
            auto pval = std::get_if<std::pair<int64_t, int64_t>>(&mElem);
            if (pval != nullptr) {
                pval->first += value.first;
                pval->second += value.second;
            } else {
                mElem = value;
            }
        }

        status_t writeToParcel(Parcel *parcel) const {
            return std::visit([this, parcel](auto &value) {
                    return BaseItem::writeToParcel(mName.c_str(), value, parcel);}, mElem);
        }

        void toStringBuffer(char *buffer, size_t length) const {
            return std::visit([this, buffer, length](auto &value) {
                BaseItem::toStringBuffer(mName.c_str(), value, buffer, length);}, mElem);
        }

        size_t getByteStringSize() const {
            return std::visit([this](auto &value) {
                return BaseItem::sizeOfByteString(mName.c_str(), value);}, mElem);
        }

        status_t writeToByteString(char **bufferpptr, char *bufferptrmax) const {
            return std::visit([this, bufferpptr, bufferptrmax](auto &value) {
                return BaseItem::writeToByteString(mName.c_str(), value, bufferpptr, bufferptrmax);
            }, mElem);
        }

        status_t readFromParcel(const Parcel& data);

        status_t readFromByteString(const char **bufferpptr, const char *bufferptrmax);

    private:
        std::string mName;
        Elem mElem;
    };

    // Iteration of props within item
    class iterator {
    public:
        explicit iterator(const std::map<std::string, Prop>::const_iterator &_it) : it(_it) { }
        iterator &operator++() {
            ++it;
            return *this;
        }
        bool operator!=(iterator &other) const {
            return it != other.it;
        }
        const Prop &operator*() const {
            return it->second;
        }

    private:
        std::map<std::string, Prop>::const_iterator it;
    };

    iterator begin() const {
        return iterator(mProps.cbegin());
    }

    iterator end() const {
        return iterator(mProps.cend());
    }

    // T must be convertible to mKey
    template <typename T>
    explicit Item(T key)
        : mKey(key) { }
    Item() = default;

    // We enable default copy and move constructors and make this class final
    // to prevent a derived class; this avoids possible data slicing.
    Item(const Item& other) = default;
    Item(Item&& other) = default;
    Item& operator=(const Item& other) = default;
    Item& operator=(Item&& other) = default;

    bool operator==(const Item& other) const {
        return mPid == other.mPid
            && mUid == other.mUid
            && mPkgName == other.mPkgName
            && mPkgVersionCode == other.mPkgVersionCode
            && mKey == other.mKey
            && mTimestamp == other.mTimestamp
            && mProps == other.mProps
            ;
    }
    bool operator!=(const Item& other) const {
        return !(*this == other);
    }

    template <typename T>
    static Item* create(T key) {
        return new Item(key);
    }
    static Item* create() {
        return new Item();
    }

        static Item* convert(mediametrics_handle_t);
        static mediametrics_handle_t convert(Item *);

        // access functions for the class
        ~Item();

    void clear() {
        mPid = -1;
        mUid = -1;
        mPkgName.clear();
        mPkgVersionCode = 0;
        mTimestamp = 0;
        mKey.clear();
        mProps.clear();
    }

    Item *dup() const { return new Item(*this); }

    Item &setKey(const char *key) {
        mKey = key;
        return *this;
    }
    const std::string& getKey() const { return mKey; }

    // # of properties in the record
    size_t count() const { return mProps.size(); }

    template<typename S, typename T>
    Item &set(S key, T value) {
        findOrAllocateProp(key).set(value);
        return *this;
    }

    // set values appropriately
    Item &setInt32(const char *key, int32_t value) {
        return set(key, value);
    }
    Item &setInt64(const char *key, int64_t value) {
        return set(key, value);
    }
    Item &setDouble(const char *key, double value) {
        return set(key, value);
    }
    Item &setRate(const char *key, int64_t count, int64_t duration) {
        return set(key, std::make_pair(count, duration));
    }
    Item &setCString(const char *key, const char *value) {
        return set(key, value);
    }

    // fused get/add/set; if attr wasn't there, it's a simple set.
    // type-mismatch counts as "wasn't there".
    template<typename S, typename T>
    Item &add(S key, T value) {
        findOrAllocateProp(key).add(value);
        return *this;
    }

    Item &addInt32(const char *key, int32_t value) {
        return add(key, value);
    }
    Item &addInt64(const char *key, int64_t value) {
        return add(key, value);
    }
    Item &addDouble(const char *key, double value) {
        return add(key, value);
    }
    Item &addRate(const char *key, int64_t count, int64_t duration) {
        return add(key, std::make_pair(count, duration));
    }

    // find & extract values
    // return indicates whether attr exists (and thus value filled in)
    // NULL parameter value suppresses storage of value.
    template<typename S, typename T>
    bool get(S key, T *value) const {
        const Prop *prop = findProp(key);
        return prop != nullptr && prop->get(value);
    }

    bool getInt32(const char *key, int32_t *value) const {
        return get(key, value);
    }
    bool getInt64(const char *key, int64_t *value) const {
        return get(key, value);
    }
    bool getDouble(const char *key, double *value) const {
        return get(key, value);
    }
    bool getRate(const char *key, int64_t *count, int64_t *duration, double *rate) const {
        std::pair<int64_t, int64_t> value;
        if (!get(key, &value)) return false;
        if (count != nullptr) *count = value.first;
        if (duration != nullptr) *duration = value.second;
        if (rate != nullptr) {
            if (value.second != 0) {
                *rate = (double)value.first / value.second;  // TODO: isn't INF OK?
            } else {
                *rate = 0.;
            }
        }
        return true;
    }
    bool getString(const char *key, std::string *value) const {
        return get(key, value);
    }
    // Caller owns the returned string
    bool getCString(const char *key, char **value) const {
        std::string s;
        if (get(key, &s)) {
            *value = strdup(s.c_str());
            return true;
        }
        return false;
    }

    const Prop::Elem* get(const char *key) const {
        const Prop *prop = findProp(key);
        return prop == nullptr ? nullptr : &prop->get();
    }

        // Deliver the item to MediaMetrics
        bool selfrecord();

    // remove indicated attributes and their values
    // filterNot() could also be called keepOnly()
    // return value is # attributes removed
    // XXX: perhaps 'remove' instead of 'filter'
    // XXX: filterNot would become 'keep'
    size_t filter(size_t count, const char *attrs[]);
    size_t filterNot(size_t count, const char *attrs[]);
    size_t filter(const char *attr) { return filter(1, &attr); }

        // below here are used on server side or to talk to server
        // clients need not worry about these.

        // timestamp, pid, and uid only used on server side
        // timestamp is in 'nanoseconds, unix time'
        Item &setTimestamp(nsecs_t);
        nsecs_t getTimestamp() const;

        Item &setPid(pid_t);
        pid_t getPid() const;

        Item &setUid(uid_t);
        uid_t getUid() const;

        Item &setPkgName(const std::string &pkgName);
        std::string getPkgName() const { return mPkgName; }

        Item &setPkgVersionCode(int64_t);
        int64_t getPkgVersionCode() const;

    // our serialization code for binder calls
    status_t writeToParcel(Parcel *) const;
    status_t readFromParcel(const Parcel&);

    status_t writeToByteString(char **bufferptr, size_t *length) const;
    status_t readFromByteString(const char *bufferptr, size_t length);


        std::string toString() const;
        const char *toCString();

    /**
     * Returns true if the item has a property with a target value.
     *
     * If propName is nullptr, hasPropElem() returns false.
     *
     * \param propName is the property name.
     * \param elem is the value to match.  std::monostate matches any.
     */
    bool hasPropElem(const char *propName, const Prop::Elem& elem) const {
        if (propName == nullptr) return false;
        const Prop::Elem *e = get(propName);
        return e != nullptr && (std::holds_alternative<std::monostate>(elem) || elem == *e);
    }

    /**
     * Returns -2, -1, 0 (success) if the item has a property (wildcard matched) with a
     * target value.
     *
     * The enum RecursiveWildcardCheck designates the meaning of the returned value.
     *
     * RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD = -2,
     * RECURSIVE_WILDCARD_CHECK_NO_MATCH_WILDCARD_FOUND = -1,
     * RECURSIVE_WILDCARD_CHECK_MATCH_FOUND = 0.
     *
     * If url is nullptr, RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD is returned.
     *
     * \param url is the full item + property name, which may have wildcards '*'
     *            denoting an arbitrary sequence of 0 or more characters.
     * \param elem is the target property value to match. std::monostate matches any.
     * \return 0 if the property was matched,
     *         -1 if the property was not matched and a wildcard char was encountered,
     *         -2 if the property was not matched with no wildcard char encountered.
     */
    enum RecursiveWildcardCheck {
        RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD = -2,
        RECURSIVE_WILDCARD_CHECK_NO_MATCH_WILDCARD_FOUND = -1,
        RECURSIVE_WILDCARD_CHECK_MATCH_FOUND = 0,
    };

    enum RecursiveWildcardCheck recursiveWildcardCheckElem(
        const char *url, const Prop::Elem& elem) const {
        if (url == nullptr) return RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD;
        return recursiveWildcardCheckElem(getKey().c_str(), url, elem);
    }

private:

    enum RecursiveWildcardCheck recursiveWildcardCheckElem(
            const char *itemKeyPtr, const char *url, const Prop::Elem& elem) const {
        for (; *url && *itemKeyPtr; ++url, ++itemKeyPtr) {
            if (*url != *itemKeyPtr) {
                if (*url == '*') { // wildcard
                    ++url;
                    while (true) {
                        if (recursiveWildcardCheckElem(itemKeyPtr, url, elem)
                                == RECURSIVE_WILDCARD_CHECK_MATCH_FOUND) {
                            return RECURSIVE_WILDCARD_CHECK_MATCH_FOUND;
                        }
                        if (*itemKeyPtr == 0) break;
                        ++itemKeyPtr;
                    }
                    return RECURSIVE_WILDCARD_CHECK_NO_MATCH_WILDCARD_FOUND;
                }
                return RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD;
            }
        }
        if (itemKeyPtr[0] != 0 || url[0] != '.') {
            return RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD;
        }
        const char *propName = url + 1; // skip the '.'
        return hasPropElem(propName, elem)
                ? RECURSIVE_WILDCARD_CHECK_MATCH_FOUND
                : RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD;
    }

    // handle Parcel version 0
    int32_t writeToParcel0(Parcel *) const;
    int32_t readFromParcel0(const Parcel&);

    const Prop *findProp(const char *key) const {
        auto it = mProps.find(key);
        return it != mProps.end() ? &it->second : nullptr;
    }

    Prop &findOrAllocateProp(const char *key) {
        auto it = mProps.find(key);
        if (it != mProps.end()) return it->second;
        Prop &prop = mProps[key];
        prop.setName(key);
        return prop;
    }

    // Changes to member variables below require changes to clear().
    pid_t         mPid = -1;
    uid_t         mUid = -1;
    std::string   mPkgName;
    int64_t       mPkgVersionCode = 0;
    std::string   mKey;
    nsecs_t       mTimestamp = 0;
    std::map<std::string, Prop> mProps;
};

} // namespace mediametrics
} // namespace android

#endif // ANDROID_MEDIA_MEDIAMETRICSITEM_H
