/*
 * Copyright (C) 2010 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.
 */

#define LOG_TAG "AMessage"
//#define LOG_NDEBUG 0
//#define DUMP_STATS

#include <ctype.h>

#include "AMessage.h"

#include <log/log.h>

#include "AAtomizer.h"
#include "ABuffer.h"
#include "ADebug.h"
#include "ALooperRoster.h"
#include "AHandler.h"
#include "AString.h"

#include <media/stagefright/foundation/hexdump.h>

#if defined(__ANDROID__) && !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
#include <binder/Parcel.h>
#endif

namespace android {

extern ALooperRoster gLooperRoster;

status_t AReplyToken::setReply(const sp<AMessage> &reply) {
    if (mReplied) {
        ALOGE("trying to post a duplicate reply");
        return -EBUSY;
    }
    CHECK(mReply == NULL);
    mReply = reply;
    mReplied = true;
    return OK;
}

AMessage::AMessage(void)
    : mWhat(0),
      mTarget(0) {
}

AMessage::AMessage(uint32_t what, const sp<const AHandler> &handler)
    : mWhat(what) {
    setTarget(handler);
}

AMessage::~AMessage() {
    clear();
}

void AMessage::setWhat(uint32_t what) {
    mWhat = what;
}

uint32_t AMessage::what() const {
    return mWhat;
}

void AMessage::setTarget(const sp<const AHandler> &handler) {
    if (handler == NULL) {
        mTarget = 0;
        mHandler.clear();
        mLooper.clear();
    } else {
        mTarget = handler->id();
        mHandler = handler->getHandler();
        mLooper = handler->getLooper();
    }
}

void AMessage::clear() {
    // Item needs to be handled delicately
    for (Item &item : mItems) {
        delete[] item.mName;
        item.mName = NULL;
        freeItemValue(&item);
    }
    mItems.clear();
}

void AMessage::freeItemValue(Item *item) {
    switch (item->mType) {
        case kTypeString:
        {
            delete item->u.stringValue;
            break;
        }

        case kTypeObject:
        case kTypeMessage:
        case kTypeBuffer:
        {
            if (item->u.refValue != NULL) {
                item->u.refValue->decStrong(this);
            }
            break;
        }

        default:
            break;
    }
    item->mType = kTypeInt32; // clear type
}

#ifdef DUMP_STATS
#include <utils/Mutex.h>

Mutex gLock;
static int32_t gFindItemCalls = 1;
static int32_t gDupCalls = 1;
static int32_t gAverageNumItems = 0;
static int32_t gAverageNumChecks = 0;
static int32_t gAverageNumMemChecks = 0;
static int32_t gAverageDupItems = 0;
static int32_t gLastChecked = -1;

static void reportStats() {
    int32_t time = (ALooper::GetNowUs() / 1000);
    if (time / 1000 != gLastChecked / 1000) {
        gLastChecked = time;
        ALOGI("called findItemIx %zu times (for len=%.1f i=%.1f/%.1f mem) dup %zu times (for len=%.1f)",
                gFindItemCalls,
                gAverageNumItems / (float)gFindItemCalls,
                gAverageNumChecks / (float)gFindItemCalls,
                gAverageNumMemChecks / (float)gFindItemCalls,
                gDupCalls,
                gAverageDupItems / (float)gDupCalls);
        gFindItemCalls = gDupCalls = 1;
        gAverageNumItems = gAverageNumChecks = gAverageNumMemChecks = gAverageDupItems = 0;
        gLastChecked = time;
    }
}
#endif

inline size_t AMessage::findItemIndex(const char *name, size_t len) const {
#ifdef DUMP_STATS
    size_t memchecks = 0;
#endif
    size_t i = 0;
    for (; i < mItems.size(); i++) {
        if (len != mItems[i].mNameLength) {
            continue;
        }
#ifdef DUMP_STATS
        ++memchecks;
#endif
        if (!memcmp(mItems[i].mName, name, len)) {
            break;
        }
    }
#ifdef DUMP_STATS
    {
        Mutex::Autolock _l(gLock);
        ++gFindItemCalls;
        gAverageNumItems += mItems.size();
        gAverageNumMemChecks += memchecks;
        gAverageNumChecks += i;
        reportStats();
    }
#endif
    return i;
}

// assumes item's name was uninitialized or NULL
void AMessage::Item::setName(const char *name, size_t len) {
    mNameLength = len;
    mName = new char[len + 1];
    memcpy((void*)mName, name, len + 1);
}

AMessage::Item::Item(const char *name, size_t len)
    : mType(kTypeInt32) {
    // mName and mNameLength are initialized by setName
    setName(name, len);
}

AMessage::Item *AMessage::allocateItem(const char *name) {
    size_t len = strlen(name);
    size_t i = findItemIndex(name, len);
    Item *item;

    if (i < mItems.size()) {
        item = &mItems[i];
        freeItemValue(item);
    } else {
        CHECK(mItems.size() < kMaxNumItems);
        i = mItems.size();
        // place a 'blank' item at the end - this is of type kTypeInt32
        mItems.emplace_back(name, len);
        item = &mItems[i];
    }

    return item;
}

const AMessage::Item *AMessage::findItem(
        const char *name, Type type) const {
    size_t i = findItemIndex(name, strlen(name));
    if (i < mItems.size()) {
        const Item *item = &mItems[i];
        return item->mType == type ? item : NULL;

    }
    return NULL;
}

bool AMessage::findAsFloat(const char *name, float *value) const {
    size_t i = findItemIndex(name, strlen(name));
    if (i < mItems.size()) {
        const Item *item = &mItems[i];
        switch (item->mType) {
            case kTypeFloat:
                *value = item->u.floatValue;
                return true;
            case kTypeDouble:
                *value = (float)item->u.doubleValue;
                return true;
            case kTypeInt64:
                *value = (float)item->u.int64Value;
                return true;
            case kTypeInt32:
                *value = (float)item->u.int32Value;
                return true;
            case kTypeSize:
                *value = (float)item->u.sizeValue;
                return true;
            default:
                return false;
        }
    }
    return false;
}

bool AMessage::findAsInt64(const char *name, int64_t *value) const {
    size_t i = findItemIndex(name, strlen(name));
    if (i < mItems.size()) {
        const Item *item = &mItems[i];
        switch (item->mType) {
            case kTypeInt64:
                *value = item->u.int64Value;
                return true;
            case kTypeInt32:
                *value = item->u.int32Value;
                return true;
            default:
                return false;
        }
    }
    return false;
}

bool AMessage::contains(const char *name) const {
    size_t i = findItemIndex(name, strlen(name));
    return i < mItems.size();
}

#define BASIC_TYPE(NAME,FIELDNAME,TYPENAME)                             \
void AMessage::set##NAME(const char *name, TYPENAME value) {            \
    Item *item = allocateItem(name);                                    \
    if (item) {                                                         \
        item->mType = kType##NAME;                                      \
        item->u.FIELDNAME = value;                                      \
    }                                                                   \
}                                                                       \
                                                                        \
/* NOLINT added to avoid incorrect warning/fix from clang.tidy */       \
bool AMessage::find##NAME(const char *name, TYPENAME *value) const {  /* NOLINT */ \
    const Item *item = findItem(name, kType##NAME);                     \
    if (item) {                                                         \
        *value = item->u.FIELDNAME;                                     \
        return true;                                                    \
    }                                                                   \
    return false;                                                       \
}

BASIC_TYPE(Int32,int32Value,int32_t)
BASIC_TYPE(Int64,int64Value,int64_t)
BASIC_TYPE(Size,sizeValue,size_t)
BASIC_TYPE(Float,floatValue,float)
BASIC_TYPE(Double,doubleValue,double)
BASIC_TYPE(Pointer,ptrValue,void *)

#undef BASIC_TYPE

void AMessage::setString(
        const char *name, const char *s, ssize_t len) {
    Item *item = allocateItem(name);
    if (item) {
        item->mType = kTypeString;
        item->u.stringValue = new AString(s, len < 0 ? strlen(s) : len);
    }
}

void AMessage::setString(
        const char *name, const AString &s) {
    setString(name, s.c_str(), s.size());
}

void AMessage::setObjectInternal(
        const char *name, const sp<RefBase> &obj, Type type) {
    Item *item = allocateItem(name);
    if (item) {
        item->mType = type;

        if (obj != NULL) { obj->incStrong(this); }
        item->u.refValue = obj.get();
    }
}

void AMessage::setObject(const char *name, const sp<RefBase> &obj) {
    setObjectInternal(name, obj, kTypeObject);
}

void AMessage::setBuffer(const char *name, const sp<ABuffer> &buffer) {
    setObjectInternal(name, sp<RefBase>(buffer), kTypeBuffer);
}

void AMessage::setMessage(const char *name, const sp<AMessage> &obj) {
    Item *item = allocateItem(name);
    if (item) {
        item->mType = kTypeMessage;

        if (obj != NULL) { obj->incStrong(this); }
        item->u.refValue = obj.get();
    }
}

void AMessage::setRect(
        const char *name,
        int32_t left, int32_t top, int32_t right, int32_t bottom) {
    Item *item = allocateItem(name);
    if (item) {
        item->mType = kTypeRect;

        item->u.rectValue.mLeft = left;
        item->u.rectValue.mTop = top;
        item->u.rectValue.mRight = right;
        item->u.rectValue.mBottom = bottom;
    }
}

bool AMessage::findString(const char *name, AString *value) const {
    const Item *item = findItem(name, kTypeString);
    if (item) {
        *value = *item->u.stringValue;
        return true;
    }
    return false;
}

bool AMessage::findObject(const char *name, sp<RefBase> *obj) const {
    const Item *item = findItem(name, kTypeObject);
    if (item) {
        *obj = item->u.refValue;
        return true;
    }
    return false;
}

bool AMessage::findBuffer(const char *name, sp<ABuffer> *buf) const {
    const Item *item = findItem(name, kTypeBuffer);
    if (item) {
        *buf = (ABuffer *)(item->u.refValue);
        return true;
    }
    return false;
}

bool AMessage::findMessage(const char *name, sp<AMessage> *obj) const {
    const Item *item = findItem(name, kTypeMessage);
    if (item) {
        *obj = static_cast<AMessage *>(item->u.refValue);
        return true;
    }
    return false;
}

bool AMessage::findRect(
        const char *name,
        int32_t *left, int32_t *top, int32_t *right, int32_t *bottom) const {
    const Item *item = findItem(name, kTypeRect);
    if (item == NULL) {
        return false;
    }

    *left = item->u.rectValue.mLeft;
    *top = item->u.rectValue.mTop;
    *right = item->u.rectValue.mRight;
    *bottom = item->u.rectValue.mBottom;

    return true;
}

void AMessage::deliver() {
    sp<AHandler> handler = mHandler.promote();
    if (handler == NULL) {
        ALOGW("failed to deliver message as target handler %d is gone.", mTarget);
        return;
    }

    handler->deliverMessage(this);
}

status_t AMessage::post(int64_t delayUs) {
    sp<ALooper> looper = mLooper.promote();
    if (looper == NULL) {
        ALOGW("failed to post message as target looper for handler %d is gone.", mTarget);
        return -ENOENT;
    }

    looper->post(this, delayUs);
    return OK;
}

status_t AMessage::postUnique(const sp<RefBase> &token, int64_t delayUs) {
    sp<ALooper> looper = mLooper.promote();
    if (looper == NULL) {
        ALOGW("failed to post message as target looper for handler %d is gone.",
              mTarget);
        return -ENOENT;
    }

    return looper->postUnique(this, token, delayUs);
}

status_t AMessage::postAndAwaitResponse(sp<AMessage> *response) {
    sp<ALooper> looper = mLooper.promote();
    if (looper == NULL) {
        ALOGW("failed to post message as target looper for handler %d is gone.", mTarget);
        return -ENOENT;
    }

    sp<AReplyToken> token = looper->createReplyToken();
    if (token == NULL) {
        ALOGE("failed to create reply token");
        return -ENOMEM;
    }
    setObject("replyID", token);

    looper->post(this, 0 /* delayUs */);
    return looper->awaitResponse(token, response);
}

status_t AMessage::postReply(const sp<AReplyToken> &replyToken) {
    if (replyToken == NULL) {
        ALOGW("failed to post reply to a NULL token");
        return -ENOENT;
    }
    sp<ALooper> looper = replyToken->getLooper();
    if (looper == NULL) {
        ALOGW("failed to post reply as target looper is gone.");
        return -ENOENT;
    }
    return looper->postReply(replyToken, this);
}

bool AMessage::senderAwaitsResponse(sp<AReplyToken> *replyToken) {
    sp<RefBase> tmp;
    bool found = findObject("replyID", &tmp);

    if (!found) {
        return false;
    }

    *replyToken = static_cast<AReplyToken *>(tmp.get());
    tmp.clear();
    setObject("replyID", tmp);
    // TODO: delete Object instead of setting it to NULL

    return *replyToken != NULL;
}

sp<AMessage> AMessage::dup() const {
    sp<AMessage> msg = new AMessage(mWhat, mHandler.promote());
    msg->mItems = mItems;

#ifdef DUMP_STATS
    {
        Mutex::Autolock _l(gLock);
        ++gDupCalls;
        gAverageDupItems += mItems.size();
        reportStats();
    }
#endif

    for (size_t i = 0; i < mItems.size(); ++i) {
        const Item *from = &mItems[i];
        Item *to = &msg->mItems[i];

        to->setName(from->mName, from->mNameLength);
        to->mType = from->mType;

        switch (from->mType) {
            case kTypeString:
            {
                to->u.stringValue =
                    new AString(*from->u.stringValue);
                break;
            }

            case kTypeObject:
            case kTypeBuffer:
            {
                to->u.refValue = from->u.refValue;
                to->u.refValue->incStrong(msg.get());
                break;
            }

            case kTypeMessage:
            {
                sp<AMessage> copy =
                    static_cast<AMessage *>(from->u.refValue)->dup();

                to->u.refValue = copy.get();
                to->u.refValue->incStrong(msg.get());
                break;
            }

            default:
            {
                to->u = from->u;
                break;
            }
        }
    }

    return msg;
}

static void appendIndent(AString *s, int32_t indent) {
    static const char kWhitespace[] =
        "                                        "
        "                                        ";

    CHECK_LT((size_t)indent, sizeof(kWhitespace));

    s->append(kWhitespace, indent);
}

static bool isFourcc(uint32_t what) {
    return isprint(what & 0xff)
        && isprint((what >> 8) & 0xff)
        && isprint((what >> 16) & 0xff)
        && isprint((what >> 24) & 0xff);
}

AString AMessage::debugString(int32_t indent) const {
    AString s = "AMessage(what = ";

    AString tmp;
    if (isFourcc(mWhat)) {
        tmp = AStringPrintf(
                "'%c%c%c%c'",
                (char)(mWhat >> 24),
                (char)((mWhat >> 16) & 0xff),
                (char)((mWhat >> 8) & 0xff),
                (char)(mWhat & 0xff));
    } else {
        tmp = AStringPrintf("0x%08x", mWhat);
    }
    s.append(tmp);

    if (mTarget != 0) {
        tmp = AStringPrintf(", target = %d", mTarget);
        s.append(tmp);
    }
    s.append(") = {\n");

    for (size_t i = 0; i < mItems.size(); ++i) {
        const Item &item = mItems[i];

        switch (item.mType) {
            case kTypeInt32:
                tmp = AStringPrintf(
                        "int32_t %s = %d", item.mName, item.u.int32Value);
                break;
            case kTypeInt64:
                tmp = AStringPrintf(
                        "int64_t %s = %lld", item.mName, item.u.int64Value);
                break;
            case kTypeSize:
                tmp = AStringPrintf(
                        "size_t %s = %d", item.mName, item.u.sizeValue);
                break;
            case kTypeFloat:
                tmp = AStringPrintf(
                        "float %s = %f", item.mName, item.u.floatValue);
                break;
            case kTypeDouble:
                tmp = AStringPrintf(
                        "double %s = %f", item.mName, item.u.doubleValue);
                break;
            case kTypePointer:
                tmp = AStringPrintf(
                        "void *%s = %p", item.mName, item.u.ptrValue);
                break;
            case kTypeString:
                tmp = AStringPrintf(
                        "string %s = \"%s\"",
                        item.mName,
                        item.u.stringValue->c_str());
                break;
            case kTypeObject:
                tmp = AStringPrintf(
                        "RefBase *%s = %p", item.mName, item.u.refValue);
                break;
            case kTypeBuffer:
            {
                sp<ABuffer> buffer = static_cast<ABuffer *>(item.u.refValue);

                if (buffer != NULL && buffer->data() != NULL && buffer->size() <= 64) {
                    tmp = AStringPrintf("Buffer %s = {\n", item.mName);
                    hexdump(buffer->data(), buffer->size(), indent + 4, &tmp);
                    appendIndent(&tmp, indent + 2);
                    tmp.append("}");
                } else {
                    tmp = AStringPrintf(
                            "Buffer *%s = %p", item.mName, buffer.get());
                }
                break;
            }
            case kTypeMessage:
                tmp = AStringPrintf(
                        "AMessage %s = %s",
                        item.mName,
                        static_cast<AMessage *>(
                            item.u.refValue)->debugString(
                                indent + strlen(item.mName) + 14).c_str());
                break;
            case kTypeRect:
                tmp = AStringPrintf(
                        "Rect %s(%d, %d, %d, %d)",
                        item.mName,
                        item.u.rectValue.mLeft,
                        item.u.rectValue.mTop,
                        item.u.rectValue.mRight,
                        item.u.rectValue.mBottom);
                break;
            default:
                TRESPASS();
        }

        appendIndent(&s, indent);
        s.append("  ");
        s.append(tmp);
        s.append("\n");
    }

    appendIndent(&s, indent);
    s.append("}");

    return s;
}

#if defined(__ANDROID__) && !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)
// static
sp<AMessage> AMessage::FromParcel(const Parcel &parcel, size_t maxNestingLevel) {
    int32_t what = parcel.readInt32();
    sp<AMessage> msg = new AMessage();
    msg->setWhat(what);

    size_t numItems = static_cast<size_t>(parcel.readInt32());
    if (numItems > kMaxNumItems) {
        ALOGE("Too large number of items clipped.");
        numItems = kMaxNumItems;
    }
    msg->mItems.resize(numItems);

    for (size_t i = 0; i < msg->mItems.size(); ++i) {
        Item *item = &msg->mItems[i];

        const char *name = parcel.readCString();
        if (name == NULL) {
            ALOGE("Failed reading name for an item. Parsing aborted.");
            msg->mItems.resize(i);
            break;
        }

        item->mType = static_cast<Type>(parcel.readInt32());
        // setName() happens below so that we don't leak memory when parsing
        // is aborted in the middle.
        switch (item->mType) {
            case kTypeInt32:
            {
                item->u.int32Value = parcel.readInt32();
                break;
            }

            case kTypeInt64:
            {
                item->u.int64Value = parcel.readInt64();
                break;
            }

            case kTypeSize:
            {
                item->u.sizeValue = static_cast<size_t>(parcel.readInt32());
                break;
            }

            case kTypeFloat:
            {
                item->u.floatValue = parcel.readFloat();
                break;
            }

            case kTypeDouble:
            {
                item->u.doubleValue = parcel.readDouble();
                break;
            }

            case kTypeString:
            {
                const char *stringValue = parcel.readCString();
                if (stringValue == NULL) {
                    ALOGE("Failed reading string value from a parcel. "
                        "Parsing aborted.");
                    msg->mItems.resize(i);
                    continue;
                    // The loop will terminate subsequently.
                } else {
                    item->u.stringValue = new AString(stringValue);
                }
                break;
            }

            case kTypeMessage:
            {
                if (maxNestingLevel == 0) {
                    ALOGE("Too many levels of AMessage nesting.");
                    return NULL;
                }
                sp<AMessage> subMsg = AMessage::FromParcel(
                        parcel,
                        maxNestingLevel - 1);
                if (subMsg == NULL) {
                    // This condition will be triggered when there exists an
                    // object that cannot cross process boundaries or when the
                    // level of nested AMessage is too deep.
                    return NULL;
                }
                subMsg->incStrong(msg.get());

                item->u.refValue = subMsg.get();
                break;
            }

            default:
            {
                ALOGE("This type of object cannot cross process boundaries.");
                return NULL;
            }
        }

        item->setName(name, strlen(name));
    }

    return msg;
}

void AMessage::writeToParcel(Parcel *parcel) const {
    parcel->writeInt32(static_cast<int32_t>(mWhat));
    parcel->writeInt32(static_cast<int32_t>(mItems.size()));

    for (const Item &item : mItems) {
        parcel->writeCString(item.mName);
        parcel->writeInt32(static_cast<int32_t>(item.mType));

        switch (item.mType) {
            case kTypeInt32:
            {
                parcel->writeInt32(item.u.int32Value);
                break;
            }

            case kTypeInt64:
            {
                parcel->writeInt64(item.u.int64Value);
                break;
            }

            case kTypeSize:
            {
                parcel->writeInt32(static_cast<int32_t>(item.u.sizeValue));
                break;
            }

            case kTypeFloat:
            {
                parcel->writeFloat(item.u.floatValue);
                break;
            }

            case kTypeDouble:
            {
                parcel->writeDouble(item.u.doubleValue);
                break;
            }

            case kTypeString:
            {
                parcel->writeCString(item.u.stringValue->c_str());
                break;
            }

            case kTypeMessage:
            {
                static_cast<AMessage *>(item.u.refValue)->writeToParcel(parcel);
                break;
            }

            default:
            {
                ALOGE("This type of object cannot cross process boundaries.");
                TRESPASS();
            }
        }
    }
}
#endif  // defined(__ANDROID__) && !defined(__ANDROID_VNDK__) && !defined(__ANDROID_APEX__)

sp<AMessage> AMessage::changesFrom(const sp<const AMessage> &other, bool deep) const {
    if (other == NULL) {
        return const_cast<AMessage*>(this);
    }

    sp<AMessage> diff = new AMessage;
    if (mWhat != other->mWhat) {
        diff->setWhat(mWhat);
    }
    if (mHandler != other->mHandler) {
        diff->setTarget(mHandler.promote());
    }

    for (const Item &item : mItems) {
        const Item *oitem = other->findItem(item.mName, item.mType);
        switch (item.mType) {
            case kTypeInt32:
                if (oitem == NULL || item.u.int32Value != oitem->u.int32Value) {
                    diff->setInt32(item.mName, item.u.int32Value);
                }
                break;

            case kTypeInt64:
                if (oitem == NULL || item.u.int64Value != oitem->u.int64Value) {
                    diff->setInt64(item.mName, item.u.int64Value);
                }
                break;

            case kTypeSize:
                if (oitem == NULL || item.u.sizeValue != oitem->u.sizeValue) {
                    diff->setSize(item.mName, item.u.sizeValue);
                }
                break;

            case kTypeFloat:
                if (oitem == NULL || item.u.floatValue != oitem->u.floatValue) {
                    diff->setFloat(item.mName, item.u.sizeValue);
                }
                break;

            case kTypeDouble:
                if (oitem == NULL || item.u.doubleValue != oitem->u.doubleValue) {
                    diff->setDouble(item.mName, item.u.sizeValue);
                }
                break;

            case kTypeString:
                if (oitem == NULL || *item.u.stringValue != *oitem->u.stringValue) {
                    diff->setString(item.mName, *item.u.stringValue);
                }
                break;

            case kTypeRect:
                if (oitem == NULL || memcmp(&item.u.rectValue, &oitem->u.rectValue, sizeof(Rect))) {
                    diff->setRect(
                            item.mName, item.u.rectValue.mLeft, item.u.rectValue.mTop,
                            item.u.rectValue.mRight, item.u.rectValue.mBottom);
                }
                break;

            case kTypePointer:
                if (oitem == NULL || item.u.ptrValue != oitem->u.ptrValue) {
                    diff->setPointer(item.mName, item.u.ptrValue);
                }
                break;

            case kTypeBuffer:
            {
                sp<ABuffer> myBuf = static_cast<ABuffer *>(item.u.refValue);
                if (myBuf == NULL) {
                    if (oitem == NULL || oitem->u.refValue != NULL) {
                        diff->setBuffer(item.mName, NULL);
                    }
                    break;
                }
                sp<ABuffer> oBuf = oitem == NULL ? NULL : static_cast<ABuffer *>(oitem->u.refValue);
                if (oBuf == NULL
                        || myBuf->size() != oBuf->size()
                        || (!myBuf->data() ^ !oBuf->data()) // data nullness differs
                        || (myBuf->data() && memcmp(myBuf->data(), oBuf->data(), myBuf->size()))) {
                    diff->setBuffer(item.mName, myBuf);
                }
                break;
            }

            case kTypeMessage:
            {
                sp<AMessage> myMsg = static_cast<AMessage *>(item.u.refValue);
                if (myMsg == NULL) {
                    if (oitem == NULL || oitem->u.refValue != NULL) {
                        diff->setMessage(item.mName, NULL);
                    }
                    break;
                }
                sp<AMessage> oMsg =
                    oitem == NULL ? NULL : static_cast<AMessage *>(oitem->u.refValue);
                sp<AMessage> changes = myMsg->changesFrom(oMsg, deep);
                if (changes->countEntries()) {
                    diff->setMessage(item.mName, deep ? changes : myMsg);
                }
                break;
            }

            case kTypeObject:
                if (oitem == NULL || item.u.refValue != oitem->u.refValue) {
                    diff->setObject(item.mName, item.u.refValue);
                }
                break;

            default:
            {
                ALOGE("Unknown type %d", item.mType);
                TRESPASS();
            }
        }
    }
    return diff;
}

size_t AMessage::countEntries() const {
    return mItems.size();
}

/* static */
size_t AMessage::maxAllowedEntries() {
    return kMaxNumItems;
}

const char *AMessage::getEntryNameAt(size_t index, Type *type) const {
    if (index >= mItems.size()) {
        *type = kTypeInt32;

        return NULL;
    }

    *type = mItems[index].mType;

    return mItems[index].mName;
}

AMessage::ItemData AMessage::getEntryAt(size_t index) const {
    ItemData it;
    if (index < mItems.size()) {
        switch (mItems[index].mType) {
            case kTypeInt32:    it.set(mItems[index].u.int32Value); break;
            case kTypeInt64:    it.set(mItems[index].u.int64Value); break;
            case kTypeSize:     it.set(mItems[index].u.sizeValue); break;
            case kTypeFloat:    it.set(mItems[index].u.floatValue); break;
            case kTypeDouble:   it.set(mItems[index].u.doubleValue); break;
            case kTypePointer:  it.set(mItems[index].u.ptrValue); break;
            case kTypeRect:     it.set(mItems[index].u.rectValue); break;
            case kTypeString:   it.set(*mItems[index].u.stringValue); break;
            case kTypeObject: {
                sp<RefBase> obj = mItems[index].u.refValue;
                it.set(obj);
                break;
            }
            case kTypeMessage: {
                sp<AMessage> msg = static_cast<AMessage *>(mItems[index].u.refValue);
                it.set(msg);
                break;
            }
            case kTypeBuffer: {
                sp<ABuffer> buf = static_cast<ABuffer *>(mItems[index].u.refValue);
                it.set(buf);
                break;
            }
            default:
                break;
        }
    }
    return it;
}

status_t AMessage::setEntryNameAt(size_t index, const char *name) {
    if (index >= mItems.size()) {
        return BAD_INDEX;
    }
    if (name == nullptr) {
        return BAD_VALUE;
    }
    if (!strcmp(name, mItems[index].mName)) {
        return OK; // name has not changed
    }
    size_t len = strlen(name);
    if (findItemIndex(name, len) < mItems.size()) {
        return ALREADY_EXISTS;
    }
    delete[] mItems[index].mName;
    mItems[index].mName = nullptr;
    mItems[index].setName(name, len);
    return OK;
}

status_t AMessage::setEntryAt(size_t index, const ItemData &item) {
    AString stringValue;
    sp<RefBase> refValue;
    sp<AMessage> msgValue;
    sp<ABuffer> bufValue;

    if (index >= mItems.size()) {
        return BAD_INDEX;
    }
    if (!item.used()) {
        return BAD_VALUE;
    }
    Item *dst = &mItems[index];
    freeItemValue(dst);

    // some values can be directly set with the getter. others need items to be allocated
    if (item.find(&dst->u.int32Value)) {
        dst->mType = kTypeInt32;
    } else if (item.find(&dst->u.int64Value)) {
        dst->mType = kTypeInt64;
    } else if (item.find(&dst->u.sizeValue)) {
        dst->mType = kTypeSize;
    } else if (item.find(&dst->u.floatValue)) {
        dst->mType = kTypeFloat;
    } else if (item.find(&dst->u.doubleValue)) {
        dst->mType = kTypeDouble;
    } else if (item.find(&dst->u.ptrValue)) {
        dst->mType = kTypePointer;
    } else if (item.find(&dst->u.rectValue)) {
        dst->mType = kTypeRect;
    } else if (item.find(&stringValue)) {
        dst->u.stringValue = new AString(stringValue);
        dst->mType = kTypeString;
    } else if (item.find(&refValue)) {
        if (refValue != NULL) { refValue->incStrong(this); }
        dst->u.refValue = refValue.get();
        dst->mType = kTypeObject;
    } else if (item.find(&msgValue)) {
        if (msgValue != NULL) { msgValue->incStrong(this); }
        dst->u.refValue = msgValue.get();
        dst->mType = kTypeMessage;
    } else if (item.find(&bufValue)) {
        if (bufValue != NULL) { bufValue->incStrong(this); }
        dst->u.refValue = bufValue.get();
        dst->mType = kTypeBuffer;
    } else {
        // unsupported item - we should not be here.
        dst->mType = kTypeInt32;
        dst->u.int32Value = 0xDEADDEAD;
        return BAD_TYPE;
    }
    return OK;
}

status_t AMessage::removeEntryAt(size_t index) {
    if (index >= mItems.size()) {
        return BAD_INDEX;
    }
    // delete entry data and objects
    delete[] mItems[index].mName;
    mItems[index].mName = nullptr;
    freeItemValue(&mItems[index]);

    // swap entry with last entry and clear last entry's data
    size_t lastIndex = mItems.size() - 1;
    if (index < lastIndex) {
        mItems[index] = mItems[lastIndex];
        mItems[lastIndex].mName = nullptr;
        mItems[lastIndex].mType = kTypeInt32;
    }
    mItems.pop_back();
    return OK;
}

status_t AMessage::removeEntryByName(const char *name) {
    if (name == nullptr) {
        return BAD_VALUE;
    }
    size_t index = findEntryByName(name);
    if (index >= mItems.size()) {
        return BAD_INDEX;
    }
    return removeEntryAt(index);
}

void AMessage::setItem(const char *name, const ItemData &item) {
    if (item.used()) {
        Item *it = allocateItem(name);
        if (it != nullptr) {
            setEntryAt(it - &mItems[0], item);
        }
    }
}

AMessage::ItemData AMessage::findItem(const char *name) const {
    return getEntryAt(findEntryByName(name));
}

void AMessage::extend(const sp<AMessage> &other) {
    // ignore null messages
    if (other == nullptr) {
        return;
    }

    for (size_t ix = 0; ix < other->mItems.size(); ++ix) {
        Item *it = allocateItem(other->mItems[ix].mName);
        if (it != nullptr) {
            ItemData data = other->getEntryAt(ix);
            setEntryAt(it - &mItems[0], data);
        }
    }
}

size_t AMessage::findEntryByName(const char *name) const {
    return name == nullptr ? countEntries() : findItemIndex(name, strlen(name));
}

}  // namespace android
