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

#include <utility>

#include "androidfw/CursorWindow.h"

#include "TestHelpers.h"

#define CREATE_WINDOW_1K \
    CursorWindow* w; \
    CursorWindow::create(String8("test"), 1 << 10, &w);

#define CREATE_WINDOW_1K_3X3 \
    CursorWindow* w; \
    CursorWindow::create(String8("test"), 1 << 10, &w); \
    ASSERT_EQ(w->setNumColumns(3), OK); \
    ASSERT_EQ(w->allocRow(), OK); \
    ASSERT_EQ(w->allocRow(), OK); \
    ASSERT_EQ(w->allocRow(), OK);

#define CREATE_WINDOW_2M \
    CursorWindow* w; \
    CursorWindow::create(String8("test"), 1 << 21, &w);

static constexpr const size_t kHalfInlineSize = 8192;
static constexpr const size_t kGiantSize = 1048576;

namespace android {

TEST(CursorWindowTest, Empty) {
    CREATE_WINDOW_1K;

    ASSERT_EQ(w->getNumRows(), 0);
    ASSERT_EQ(w->getNumColumns(), 0);
    ASSERT_EQ(w->size(), 1 << 10);
    ASSERT_EQ(w->freeSpace(), 1 << 10);
}

TEST(CursorWindowTest, SetNumColumns) {
    CREATE_WINDOW_1K;

    // Once we've locked in columns, we can't adjust
    ASSERT_EQ(w->getNumColumns(), 0);
    ASSERT_EQ(w->setNumColumns(4), OK);
    ASSERT_NE(w->setNumColumns(5), OK);
    ASSERT_NE(w->setNumColumns(3), OK);
    ASSERT_EQ(w->getNumColumns(), 4);
}

TEST(CursorWindowTest, SetNumColumnsAfterRow) {
    CREATE_WINDOW_1K;

    // Once we've locked in a row, we can't adjust columns
    ASSERT_EQ(w->getNumColumns(), 0);
    ASSERT_EQ(w->allocRow(), OK);
    ASSERT_NE(w->setNumColumns(4), OK);
    ASSERT_EQ(w->getNumColumns(), 0);
}

TEST(CursorWindowTest, AllocRow) {
    CREATE_WINDOW_1K;

    ASSERT_EQ(w->setNumColumns(4), OK);

    // Rolling forward means we have less free space
    ASSERT_EQ(w->getNumRows(), 0);
    auto before = w->freeSpace();
    ASSERT_EQ(w->allocRow(), OK);
    ASSERT_LT(w->freeSpace(), before);
    ASSERT_EQ(w->getNumRows(), 1);

    // Verify we can unwind
    ASSERT_EQ(w->freeLastRow(), OK);
    ASSERT_EQ(w->freeSpace(), before);
    ASSERT_EQ(w->getNumRows(), 0);

    // Can't unwind when no rows left
    ASSERT_NE(w->freeLastRow(), OK);
}

TEST(CursorWindowTest, AllocRowBounds) {
    CREATE_WINDOW_1K;

    // 60 columns is 960 bytes, which means only a single row can fit
    ASSERT_EQ(w->setNumColumns(60), OK);
    ASSERT_EQ(w->allocRow(), OK);
    ASSERT_NE(w->allocRow(), OK);
}

TEST(CursorWindowTest, StoreNull) {
    CREATE_WINDOW_1K_3X3;

    ASSERT_EQ(w->putNull(1, 1), OK);
    ASSERT_EQ(w->putNull(0, 0), OK);

    {
        auto field = w->getFieldSlot(1, 1);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL);
    }
    {
        auto field = w->getFieldSlot(0, 0);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL);
    }
}

TEST(CursorWindowTest, StoreLong) {
    CREATE_WINDOW_1K_3X3;

    ASSERT_EQ(w->putLong(1, 1, 0xf00d), OK);
    ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);

    {
        auto field = w->getFieldSlot(1, 1);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
        ASSERT_EQ(w->getFieldSlotValueLong(field), 0xf00d);
    }
    {
        auto field = w->getFieldSlot(0, 0);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
        ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe);
    }
}

TEST(CursorWindowTest, StoreString) {
    CREATE_WINDOW_1K_3X3;

    ASSERT_EQ(w->putString(1, 1, "food", 5), OK);
    ASSERT_EQ(w->putString(0, 0, "cafe", 5), OK);

    size_t size;
    {
        auto field = w->getFieldSlot(1, 1);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_STRING);
        auto actual = w->getFieldSlotValueString(field, &size);
        ASSERT_EQ(std::string(actual), "food");
    }
    {
        auto field = w->getFieldSlot(0, 0);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_STRING);
        auto actual = w->getFieldSlotValueString(field, &size);
        ASSERT_EQ(std::string(actual), "cafe");
    }
}

TEST(CursorWindowTest, StoreBounds) {
    CREATE_WINDOW_1K_3X3;

    // Can't work with values beyond bounds
    ASSERT_NE(w->putLong(0, 3, 0xcafe), OK);
    ASSERT_NE(w->putLong(3, 0, 0xcafe), OK);
    ASSERT_NE(w->putLong(3, 3, 0xcafe), OK);
    ASSERT_EQ(w->getFieldSlot(0, 3), nullptr);
    ASSERT_EQ(w->getFieldSlot(3, 0), nullptr);
    ASSERT_EQ(w->getFieldSlot(3, 3), nullptr);

    // Can't work with invalid indexes
    ASSERT_NE(w->putLong(-1, 0, 0xcafe), OK);
    ASSERT_NE(w->putLong(0, -1, 0xcafe), OK);
    ASSERT_NE(w->putLong(-1, -1, 0xcafe), OK);
    ASSERT_EQ(w->getFieldSlot(-1, 0), nullptr);
    ASSERT_EQ(w->getFieldSlot(0, -1), nullptr);
    ASSERT_EQ(w->getFieldSlot(-1, -1), nullptr);
}

TEST(CursorWindowTest, Inflate) {
    CREATE_WINDOW_2M;

    auto before = w->size();
    ASSERT_EQ(w->setNumColumns(4), OK);
    ASSERT_EQ(w->allocRow(), OK);

    // Scratch buffer that will fit before inflation
    void* buf = malloc(kHalfInlineSize);

    // Store simple value
    ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);

    // Store first object that fits inside
    memset(buf, 42, kHalfInlineSize);
    ASSERT_EQ(w->putBlob(0, 1, buf, kHalfInlineSize), OK);
    ASSERT_EQ(w->size(), before);

    // Store second simple value
    ASSERT_EQ(w->putLong(0, 2, 0xface), OK);

    // Store second object that requires inflation
    memset(buf, 84, kHalfInlineSize);
    ASSERT_EQ(w->putBlob(0, 3, buf, kHalfInlineSize), OK);
    ASSERT_GT(w->size(), before);

    // Verify data is intact
    {
        auto field = w->getFieldSlot(0, 0);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
        ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe);
    }
    {
        auto field = w->getFieldSlot(0, 1);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB);
        size_t actualSize;
        auto actual = w->getFieldSlotValueBlob(field, &actualSize);
        ASSERT_EQ(actualSize, kHalfInlineSize);
        memset(buf, 42, kHalfInlineSize);
        ASSERT_NE(actual, buf);
        ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0);
    }
    {
        auto field = w->getFieldSlot(0, 2);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
        ASSERT_EQ(w->getFieldSlotValueLong(field), 0xface);
    }
    {
        auto field = w->getFieldSlot(0, 3);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB);
        size_t actualSize;
        auto actual = w->getFieldSlotValueBlob(field, &actualSize);
        ASSERT_EQ(actualSize, kHalfInlineSize);
        memset(buf, 84, kHalfInlineSize);
        ASSERT_NE(actual, buf);
        ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0);
    }
}

TEST(CursorWindowTest, ParcelEmpty) {
    CREATE_WINDOW_2M;

    Parcel p;
    w->writeToParcel(&p);
    p.setDataPosition(0);
    w = nullptr;

    ASSERT_EQ(CursorWindow::createFromParcel(&p, &w), OK);
    ASSERT_EQ(w->getNumRows(), 0);
    ASSERT_EQ(w->getNumColumns(), 0);
    ASSERT_EQ(w->size(), 0);
    ASSERT_EQ(w->freeSpace(), 0);

    // We can't mutate the window after parceling
    ASSERT_NE(w->setNumColumns(4), OK);
    ASSERT_NE(w->allocRow(), OK);
}

TEST(CursorWindowTest, ParcelSmall) {
    CREATE_WINDOW_2M;

    auto before = w->size();
    ASSERT_EQ(w->setNumColumns(4), OK);
    ASSERT_EQ(w->allocRow(), OK);

    // Scratch buffer that will fit before inflation
    void* buf = malloc(kHalfInlineSize);

    // Store simple value
    ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);

    // Store first object that fits inside
    memset(buf, 42, kHalfInlineSize);
    ASSERT_EQ(w->putBlob(0, 1, buf, kHalfInlineSize), OK);
    ASSERT_EQ(w->size(), before);

    // Store second object with zero length
    ASSERT_EQ(w->putBlob(0, 2, buf, 0), OK);
    ASSERT_EQ(w->size(), before);

    // Force through a parcel
    Parcel p;
    w->writeToParcel(&p);
    p.setDataPosition(0);
    w = nullptr;

    ASSERT_EQ(CursorWindow::createFromParcel(&p, &w), OK);
    ASSERT_EQ(w->getNumRows(), 1);
    ASSERT_EQ(w->getNumColumns(), 4);

    // Verify data is intact
    {
        auto field = w->getFieldSlot(0, 0);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
        ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe);
    }
    {
        auto field = w->getFieldSlot(0, 1);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB);
        size_t actualSize;
        auto actual = w->getFieldSlotValueBlob(field, &actualSize);
        ASSERT_EQ(actualSize, kHalfInlineSize);
        memset(buf, 42, kHalfInlineSize);
        ASSERT_NE(actual, buf);
        ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0);
    }
    {
        auto field = w->getFieldSlot(0, 2);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB);
        size_t actualSize;
        auto actual = w->getFieldSlotValueBlob(field, &actualSize);
        ASSERT_EQ(actualSize, 0);
        ASSERT_NE(actual, nullptr);
    }
}

TEST(CursorWindowTest, ParcelLarge) {
    CREATE_WINDOW_2M;

    ASSERT_EQ(w->setNumColumns(4), OK);
    ASSERT_EQ(w->allocRow(), OK);

    // Store simple value
    ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);

    // Store object that forces inflation
    void* buf = malloc(kGiantSize);
    memset(buf, 42, kGiantSize);
    ASSERT_EQ(w->putBlob(0, 1, buf, kGiantSize), OK);

    // Store second object with zero length
    ASSERT_EQ(w->putBlob(0, 2, buf, 0), OK);

    // Force through a parcel
    Parcel p;
    w->writeToParcel(&p);
    p.setDataPosition(0);
    w = nullptr;

    ASSERT_EQ(CursorWindow::createFromParcel(&p, &w), OK);
    ASSERT_EQ(w->getNumRows(), 1);
    ASSERT_EQ(w->getNumColumns(), 4);

    // Verify data is intact
    {
        auto field = w->getFieldSlot(0, 0);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER);
        ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe);
    }
    {
        auto field = w->getFieldSlot(0, 1);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB);
        size_t actualSize;
        auto actual = w->getFieldSlotValueBlob(field, &actualSize);
        ASSERT_EQ(actualSize, kGiantSize);
        memset(buf, 42, kGiantSize);
        ASSERT_EQ(memcmp(buf, actual, kGiantSize), 0);
    }
    {
        auto field = w->getFieldSlot(0, 2);
        ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB);
        size_t actualSize;
        auto actual = w->getFieldSlotValueBlob(field, &actualSize);
        ASSERT_EQ(actualSize, 0);
        ASSERT_NE(actual, nullptr);
    }
}

} // android